C_INCLUDE_PATH
、CPLUS_INCLUDE_PATH
以及CPATH
常被用于在全局性地添加预处理C/C++时的包含目录,其中C_INCLUDE_PATH
仅对预处理C有效,CPLUS_INCLUDE_PATH
仅对预处理C++有效,而CPATH
对所有语言均有效。下面我们仅以C_INCLUDE_PATH
为例来讨论。
常用的容易出错的设置方法是在~/.bashrc
等文件中简单地使用递归式赋值:
export C_INCLUDE_PATH=$C_INCLUDE_PATH:/somewhere/include
这条语句的命令会将C_INCLUDE_PATH
赋值为它原本的值之后再附加上:/somewhere/include
而组成的值。
例如,如果原本$C_INCLUDE_PATH
的值为/previous/include
,那么执行过这一条shell语句后,$C_INCLUDE_PATH
的值就会变为/previous/include:/somewhere/include
,而如果原本没有定义过环境变量C_INCLUDE_PATH
或定义了但值为空,那么$C_INCLUDE_PATH
的值就会变为:/somewhere/include
.
可为什么说这样写是容易出错的呢?
我们知道在Linux中是使用冒号:
分隔两个目录的(Windows下常用分号;
),我们使用这条语句期望达成的效果应该是添加/somewhere/include
到预处理C时的包含目录。
GCC的官方文档影响GCC的环境变量中有这样一句:
In all these variables, an empty element instructs the compiler to search its current working directory. Empty elements can appear at the beginning or end of a path. For instance, if the value of CPATH is :/special/include
, that has the same effect as -I. -I/special/include
.
意思是:在所有这些变量中,一个空的元素会指示编译器搜索当前工作目录。空的元素可以出现在一个路径的开头或结尾。例如,如果CPATH
的值是:/special/include
,这将会等效于-I. -I/special/include
.
因此在之前的例子中,我们只想在预处理搜索路径中添加一个/somewhere/include
,期望的C_INCLUDE_PATH
的值是/somewhere/include
,但实际上C_INCLUDE_PATH
却被赋值为了:/somewhere/include
,开头的冒号之前虽然为空,却会为预处理搜索路径添加上当前工作目录,也即Linux中的.
,且优先级是所有目录中最高的!
而这往往并不是我们想要的结果,这会导致预处理的包含目录随着当前工作目录的改变而改变,如果当前工作目录中恰好有一个与源代码中包含的文件同名的文件,则会导致不可预期的错误。
不巧的是,VSCode在为我们自动生成的tasks.json
中,默认的tasks.options.cwd
恰好是/usr/bin
,因此一旦错误地设置了C_INCLUDE_PATH
的值,同时/usr/bin
中恰好有一个同名的文件(要知道这里的文件大多都是可执行程序),那么在编译C程序时,就会错把这个同名文件当作源代码文件包含进来,接下来就可以欣赏海量的编译错误了。譬如目前最受欢迎的Linux发行版Manjaro,在安装好后的/usr/bin
中就会有一个名为array
的可执行程序,与C++的<array>
重名。
正确方法:
首先明确一点,这几个预处理包含目录的环境变量并不是Linux操作系统的一部分,因此一般情况下Linux是不会设置这些环境变量的。
所以在对某一个环境变量第一次设置时,应该直接将其赋值为所需的目录,在之后的设置中再使用递归式的赋值;或者直接一次性将所有目录用:
分隔开,一起赋值;或者索性不使用这些环境变量,而是在编译时使用-I
参数来添加包含目录。
其他:
查看gcc预处理C时的的搜索目录:
echo | gcc -x c -v -E -
查看gcc预处理C++时的的搜索目录:
echo | gcc -x c++ -v -E -
查看clang预处理C++时的搜索目录:
echo | clang -x c++ -v -E -
cwd : current working directory 当前工作目录