2.1 Include语法
用户头文件和系统头文件都通过预处理指令 #include
来包含。它有两种形式:
#include <file>
这种形式用于系统头文件。它会在标准的系统目录列表中搜索名为 file
的文件。你可以使用 -I
选项(参见 调用选项)向该列表中添加目录。
#include "file"
这种形式用于你程序中的头文件。它首先在当前文件所在的目录中搜索名为 file
的文件,然后在引号目录(quote directories)中搜索,最后在与 <file>
相同的目录中搜索。你可以使用 -iquote
选项向引号目录列表中添加目录。
#include
的参数(无论是用引号还是尖括号括起来)的行为类似于字符串常量:注释不会被识别,宏名称也不会被展开。因此,#include <x/*y>
指定了一个名为 x/*y
的系统头文件。
然而,如果文件名中包含反斜杠(\
),它们会被视为普通文本字符,而不是转义字符。C 语言字符串常量中适用的字符转义序列不会被处理。因此,#include "x\n\\y"
指定的是一个包含三个反斜杠的文件名。(某些系统将 \
解释为路径分隔符,所有这些系统也将 /
解释为相同的方式。为了最大程度的可移植性,建议仅使用 /
。)
如果在文件名之后的行上还有其他内容(注释除外),则会导致错误。
2.2 Include操作
#include
指令的作用是指示 C 预处理器在继续处理当前文件的其余部分之前,先扫描指定的文件作为输入。预处理器的输出包含以下内容:已经生成的输出,接着是被包含文件产生的输出,最后是 #include
指令之后文本生成的输出。例如,如果你有一个头文件 header.h
,其内容如下:
char *test (void);
以及一个使用该头文件的主程序 program.c
,内容如下:
int x;
#include "header.h"
int
main (void)
{
puts (test ());
}
编译器看到的标记流将与以下代码等效:
int x;
char *test (void);
int
main (void)
{
puts (test ());
}
被包含的文件不仅限于声明和宏定义;这只是它们的典型用途。C 程序的任何片段都可以从另一个文件中包含进来。被包含的文件甚至可以包含一条语句的开头部分,而这条语句在包含它的文件中结束;或者它可能包含一条语句的结尾部分,而这条语句在包含它的文件中开始。然而,被包含的文件必须由完整的标记组成。如果注释或字符串字面量在被包含文件的末尾未关闭,则是无效的。为了错误恢复,这些未关闭的内容会被视为在文件末尾结束。
为了避免混淆,最好确保头文件只包含完整的语法单元——函数声明或定义、类型声明等。
即使被包含文件缺少末尾的换行符,#include
指令后的下一行也始终被 C 预处理器视为单独的一行。
2.3 搜索路径
默认情况下,预处理器会首先在当前文件所在目录中查找由引号形式的指令 #include "file"
包含的头文件,然后在预配置的标准系统目录列表中查找。例如,如果 /usr/include/sys/stat.h
包含 #include "types.h"
,GCC 会首先在 /usr/include/sys
中查找 types.h
,然后在其通常的搜索路径中查找。
对于尖括号形式的 #include <file>
,预处理器的默认行为是仅在标准系统目录中查找。确切的搜索目录列表取决于目标系统、GCC 的配置方式以及安装位置。你可以通过使用 -v
选项调用 CPP 来查看你的版本的默认搜索目录列表。例如:
bash
cpp -v /dev/null -o /dev/null
你可以使用一些命令行选项来向搜索路径中添加额外的目录。最常用的选项是 -Idir
,它会使 dir
在当前目录之后(对于引号形式的指令)和标准系统目录之前被搜索。你可以在命令行中指定多个 -I
选项,在这种情况下,目录会按照从左到右的顺序进行搜索。
如果你需要对引号形式和尖括号形式的 #include
指令的搜索路径进行单独控制,可以使用 -iquote
和/或 -isystem
选项,而不是 -I
。有关这些选项以及其他较少使用的选项的详细说明,请参见 调用选项(Invocation)。
如果你在命令行上指定了其他影响预处理器搜索头文件路径的选项(如 -I
),那么由 -v
选项打印的目录列表将反映预处理器实际使用的搜索路径。
请注意,你可以使用 -nostdinc
选项阻止预处理器搜索任何默认的系统头文件目录。这在编译操作系统内核或其他不使用标准 C 库功能的程序时非常有用,或者在编译标准 C 库本身时也可以使用该选项。