-iSystem 在系统包含目录导致错误



下面的代码是怎么回事?

#include <cmath>
int
main(int argc, char *argv[])
{
}

当在最近使用 GCC 6.1.1 安装的 Arch Linux 上编译时,它-isystem /usr/include标志会产生:

$ g++ -isystem /usr/include math.cc 
In file included from math.cc:1:0:
/usr/include/c++/6.1.1/cmath:45:23: fatal error: math.h: No such file or directory
 #include_next <math.h>
                       ^
compilation terminated.

这是一个非常简化的示例;原始命令行是:

$ g++ ... -isystem `llvm-config -includedir` ...

对于使用 LLVM 的程序的一部分。在 Arch Linux 上,LLVM 软件包安装在 /usr/include 中,这是 llvm-config 报告的目录。...包括-Wextra-Wconversion,这会导致LLVM标头中的警告。与-I相反,-isystem标志通过将LLVM目录视为"系统标头"来防止警告。有关更多信息,请参阅 GNU C 预处理器文档。

但是升级到GCC 6.1.1时,上面的错误会出现在构建中。

除了考虑目录包含"系统标头"之外,-isystem还更改了标头搜索列表,将目录参数放在系统标头目录的顶部。如果该目录已存在于搜索列表中,则会将其从其当前位置删除。

从(至少(GCC 6.1.1开始,一些C++标头(例如cmath使用#include_next来猴子补丁C++对标准C标头的支持。请参阅为什么 cstdlib><比您想象的要复杂,了解更多信息。例如,cmath有以下行:>

#include_next <math.h>

与普通的 #include 语句不同,#include_next 从包含目录搜索路径中的下一个条目开始搜索文件,而不是在搜索路径的顶部。由于-isystem /usr/include在搜索路径中移动/usr/include在包含cmath的目录之前,因此找不到math.h

详细地说,命令g++ -I /usr/include的搜索路径为

 /usr/include/c++/6.1.1
 /usr/include/c++/6.1.1/x86_64-pc-linux-gnu
 /usr/include/c++/6.1.1/backward
 /usr/lib/gcc/x86_64-pc-linux-gnu/6.1.1/include
 /usr/local/include
 /usr/lib/gcc/x86_64-pc-linux-gnu/6.1.1/include-fixed
 /usr/include

(/usr/include是一个系统目录;-I 参数不执行任何操作。

cmath位于路径/usr/include/c++/6.1.1/cmath,这是搜索路径的第一个元素。 math.h可以在

/usr/include/math.h
/usr/include/c++/6.1.1/math.h

cmath中使用#include_next <math.h>可确保跳过/usr/include/c++/6.1.1math.h的副本,并且使用的副本是/usr/include/math.h的。

对于g++ -isystem /usr/include,搜索路径为

 /usr/include
 /usr/include/c++/6.1.1
 /usr/include/c++/6.1.1/x86_64-pc-linux-gnu
 /usr/include/c++/6.1.1/backward
 /usr/lib/gcc/x86_64-pc-linux-gnu/6.1.1/include
 /usr/local/include
 /usr/lib/gcc/x86_64-pc-linux-gnu/6.1.1/include-fixed
现在

使用 #include_next <math.h> 跳过/usr/include/c++/6.1.1,但也跳过/usr/include,它在搜索路径中位于它之上。因此,编译器找不到 math.h 的任何副本。

总而言之,在使用-isystem时要小心,因为它的错误沉默副作用;如果包含的目录已经在搜索路径上,则路径的顺序可能会被修改,GCC 可能会报告错误。

类似于以下Makefile解决方法就足够了:

llvm.include.dir := $(shell $(LLVM_CONFIG) --includedir)
include.paths := $(shell echo | cc -v -E - 2>&1)
ifeq (,$(findstring $(llvm.include.dir),$(include.paths)))
# LLVM include directory is not in the existing paths;
# put it at the top of the system list
llvm.include := -isystem $(llvm.include.dir)
else
# LLVM include directory is already on the existing paths;
# do nothing
llvm.include :=
endif

这会将make变量llvm.include设置为-isystem <dir>或无,具体取决于是否实际需要它。

最新更新