Skip to content

一些GNU C的疑问

问:在C语言程序中使用#include<stdio.h>时,为什么编译器知道文件stdio.h的位置?

答:当在 C 语言程序中使用 #include <stdio.h> 时,编译器会根据预定义的搜索路径来查找头文件 stdio.h,然后再搜索用户自定义的路径。

问:如何查看预定义的搜索路径?

答:可以使用编译器的预处理选项来查看系统默认的包含路径。对于 GCC 编译器,可以使用 -E 选项来进行预处理,并使用 -v 选项查看详细的编译信息,包括默认的包含路径。

在终端中执行以下命令可以查看 GCC 编译器的默认包含路径:

bash
gcc -E -v -xc /dev/null

这个命令会输出 GCC 编译器的详细信息,其中包括搜索路径。搜索路径会以 #include <...> 形式显示在输出中,它们就是系统默认的包含路径。

输出示例:

bash
#include "..." 搜索从这里开始:
#include <...> 搜索从这里开始:
 /usr/local/lib/gcc/x86_64-pc-linux-gnu/12.1.0/include
 /usr/local/include
 /usr/local/lib/gcc/x86_64-pc-linux-gnu/12.1.0/include-fixed
 /usr/include
搜索列表结束。

可以看到,以#include "..."引入的头文件,没有搜索路径;以#include <...>引入的头文件,搜索路径如上所示。

其中头文件 stdio.h就位于/usr/include下。常见的stdlib.hstring.hstrings.hmath.htime.h均在这个目录下。

问:如何自定义编译器的搜索路径?

答:3.16 Options for Directory Search - GCC Docs

可以使用编译器的选项来指定其他搜索路径。例如,对于 GCC 编译器,可以使用 -I 选项来添加自定义的头文件搜索路径。比如:

bash
gcc -I /path/to/custom/includes my_program.c -o my_program

这样就能将 /path/to/custom/includes 添加到头文件搜索路径中,让编译器能够找到你自定义的头文件。注意,如果有多个自定义的头文件路径,可以使用多个-I参数分别指定它们。

示例:

bash
[root@centos-llvm test] % ls
include  my_program.c
[root@centos-llvm test] % ls include/
header.h
[root@centos-llvm test] % cat include/header.h 
#include<stdio.h>

int add(int a, int b){
	return a+b;
}
[root@centos-llvm test] % cat my_program.c 
#include "header.h"

int main(){
	printf("%d
",add(1,2));
}
[root@centos-llvm test] % gcc my_program.c -o my_program.o
my_program.c:1:10: 致命错误:header.h:没有那个文件或目录
    1 | #include "header.h"
      |          ^~~~~~~~~~
编译中断。
[root@centos-llvm test] % gcc -I include my_program.c -o my_program.o
[root@centos-llvm test] % ./my_program.o 
3
[root@centos-llvm test] % exit

问:如何将自定义的路径添加进编译器的默认搜索路径,让我可以通过#include <...>引入自定义的头文件?

答:在Linux系统上,一种常用的方法是将自定义路径添加到环境变量C_INCLUDE_PATHCPLUS_INCLUDE_PATH中,这取决于使用的是C语言还是C++语言。这两个环境变量指定了GCC编译器的系统包含路径。

具体步骤:

  1. 假设你的自定义头文件所在的文件夹路径为/path/to/custom/includes
  2. 如果你使用的是C语言,将自定义路径添加到C_INCLUDE_PATH环境变量:
bash
export C_INCLUDE_PATH=/path/to/custom/includes:$C_INCLUDE_PATH

如果你使用的是C++语言,将自定义路径添加到CPLUS_INCLUDE_PATH环境变量:

bash
export CPLUS_INCLUDE_PATH=/path/to/custom/includes:$CPLUS_INCLUDE_PATH

注意,通过环境变量C_INCLUDE_PATHCPLUS_INCLUDE_PATH配置搜索路径,会讲其配置的搜索路径加入到编译器预定义的路径的首部:

bash
[root@centos-llvm include]# export C_INCLUDE_PATH=/root/test/include
[root@centos-llvm include]# gcc -E -v -xc /dev/null
...
忽略不存在的目录“/usr/local/lib/gcc/x86_64-pc-linux-gnu/12.1.0/../../../../x86_64-pc-linux-gnu/include”
#include "..." 搜索从这里开始:
#include <...> 搜索从这里开始:
 /root/test/include
 /usr/local/lib/gcc/x86_64-pc-linux-gnu/12.1.0/include
 /usr/local/include
 /usr/local/lib/gcc/x86_64-pc-linux-gnu/12.1.0/include-fixed
 /usr/include

问:编译器搜索头文件的顺序?

答:3.16 Options for Directory Search - GCC Docs

顺序如下:

  • 对于#include "..."引用的头文件,会首先搜索当前文件所在目录。
  • 对于#include "..."引用的头文件,会搜索-iquote指定的路径,如有多个,则按照命令行中从左向右的先后顺序搜索。
  • -I指定的路径,如有多个,则按照命令行中从左向右的先后顺序搜索。
  • -isystem指定的路径,如有多个,则按照命令行中从左向右的先后顺序搜索。
  • 编译器预定义的路径。
  • -idirafter指定的路径,如有多个,则按照命令行中从左向右的先后顺序搜索。

问:在使用gcc编译程序的时候,为什么有的flag与参数之间没有空格?如gcc -I include my_program.cgcc -Iinclude my_program.c是等价的。

答:在使用gcc编译程序时,有的flag与参数之间没有空格是因为gcc编译器对于某些选项允许紧凑写法,即可以将选项与参数写在一起,也可以使用空格将它们分开。这是为了方便编译命令的书写,使命令更加简洁。

在这种紧凑写法中,不同的选项之间需要使用空格或其他分隔符来分开,以便gcc能够正确解析参数。但对于某些简单的选项,如 -I,在参数之后紧跟着没有其他选项的情况下,可以直接写在一起,而不需要空格分隔。

需要注意的是,虽然紧凑写法使得命令更简洁,但也容易引起歧义或混淆,建议在书写编译命令时保持一致的风格,以提高代码的可读性和可维护性。