下面的代码是怎么回事?
#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.1
中math.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>
或无,具体取决于是否实际需要它。