目录
-I <头文件PATH>一定会最先搜索吗?
并不一定,如果是尖括号<>系统头文件路径,-I会优先搜索,但如果是双引号""包含,就不是最高优先级搜索,详情参考如下。
头文件路径搜索顺序五花八门,有没有规律?
头文件搜索顺序规则
以GCC为例,用户头文件""和系统头文件<>先后顺序规则如下:
- “”
- 被编译的源代码文件所在当前目录
- -iquote指定的路径(只用于引号括起来头文件,如多个路径,从左到右依次搜索)
- -I指定的路径(如多个路径,从左到右依次搜索)
- -isystem指定的路径(如多个路径,从左到右依次搜索)
- 标准系统头文件路径
- -idirafter指定的路径(如多个路径,从左到右依次搜索)
- <>
- -I指定的路径(如多个路径,从左到右依次搜索)
- -isystem指定的路径(如多个路径,从左到右依次搜索)
- 标准系统头文件路径
- -idirafter指定的路径(如多个路径,从左到右依次搜索)
我曾经写过测试代码,证明了如上规则的正确性。当然,使用man gcc搜索-iquote也可以找到规则。
覆盖系统头文件
- 找到如上规则"标准系统头文件路径"所在的位置,在此之前的路径(或选项指定的路径)都有机会达到覆盖系统头文件的可能。即,手写一个stdio.h, 放在源代码所在的路径,#include "stdio.h"不会再找系统头文件。类似的,利用-I .指定当前目录为系统头文件路径,#include <stdio.h>也不会再找系统stdio.h.
搜索路径
不同路径存在相同的头文件,头文件搜索也有自己的规则。库文件同样有类似搜索路径问题。
GCC头文件
GCC编译C代码,根据是""或<>方式不同,头文件路径搜索顺序有差异。
- ""
- 被编译的源代码文件所在当前目录
- -iquote指定的路径(只用于引号括起来头文件,如多个路径,从左到右依次搜索)
- -I指定的路径(如多个路径,从左到右依次搜索)
- -isystem指定的路径(如多个路径,从左到右依次搜索)
- 标准系统头文件路径
- -idirafter指定的路径(如多个路径,从左到右依次搜索)
- <>
- -I指定的路径(如多个路径,从左到右依次搜索)
- -isystem指定的路径(如多个路径,从左到右依次搜索)
- 标准系统头文件路径
- -idirafter指定的路径(如多个路径,从左到右依次搜索)
有兴趣的可以写多个不同路径的相同名称头文件,用""或<>测试上面的顺序,可以一一验证。比较有趣的是,即使是系统头文件<stdio.h>, 也可以改成"stdio.h", 这样就遵循上面""的搜索顺序,可以达到覆盖系统头文件的效果。
MSVC头文件
- ""
- 源代码所在目录
- /I 指定的路径
- 标准系统头文件路径
- <>
- /I 指定的路径
- 标准系统头文件路径
GCC库文件
库文件不像头文件那样有这么多分类:
- -L参数
- LIBRARY_PATH或LD_LIBRARY_PATH
- /etc/ld.so.conf路径
- 系统默认的库路径(如/lib, /usr/lib等)。
系统头文件路径
- MSVC
可在VS工程设置"VC++目录" -> "包含目录"中查看所有系统头文件路径。
例如:
C:\VS2022\Community\VC\Tools\MSVC\14.30.30705\include
C:\VS2022\Community\VC\Tools\MSVC\14.30.30705\atlmfc\include
C:\VS2022\Community\VC\Auxiliary\VS\include
C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt
C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um
C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared
C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\winrt
C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\cppwinrt
C:\Program Files (x86)\Windows Kits\NETFXSDK\4.8\Include\um
为什么会有头文件循环依赖?
有头文件的编程语言体系中,头文件的本意是给其他文件提供基本的类型、声明或者宏等讯息供参考,让编译器知晓这些类型讯息。一般的原则是,越是common的头文件会被其他头文件依赖,即xxx.h很可能会包含common.h. 但定义common以及不同头文件的依赖很可能陷入一个困境,大家都想要对方头文件的讯息,这就出现了头文件循环依赖。
- 一种解法是更好地规划头文件。
- 另外一种解法,C/ObjC/C++都提供前向声明解决循环依赖。例如C语言的struct xxx, ObjC的@class xxx, C++的class xxx.
头文件并不是编译单元?
经常写C/C++代码的人,会注意到,编译器提示的日志基本不会包含.h的编译,除非是.h有报错才会提示.h的讯息。这是因为,C/C++的编译单元是源代码.c或.cpp, 而非.h. .h只是辅助.c/.cpp而已,给编译器看,编译器根本没把.h当人。
- 编译单元(Compilation Unit)是编程语言和编译器中的一个概念,它指的是源代码文件在编译过程中被编译器处理的最小单位。
- 大部分编程语言的编译单元都是源代码文件。
如何得到编译器预处理头文件搜索路径?
gcc可以通过--verbose获取。例如gcc --verbose demo.c
#include "…" search starts here:
#include <…> search starts here:
/usr/lib/gcc/x86_64-linux-gnu/11/include
/usr/local/include
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.
为什么C语言头文件路径不用遵循转义字符规则?
因为头文件包含是预处理完成,而非编译器。
如何包含头文件最多只有一次?
早期,标准的C语言采用#ifndef/#define的模式让头文件永远只能被包含一次,避免重复。事实上,这种写法更多是利用了条件编译宏,如果不小心将不同头文件用了同一个宏判断,会出现稀奇古怪的错误。ObjC引入了#import包含头文件,以编译器做担保,只能包含一次。一种非标准,但是不少编译器支持的新写法是#pragma once,GCC、Clang、MSVC都支持这种扩展写法。
ObjC利用#import引入头文件,头文件就不需要再像以往C语言那样加上#ifndef/#define避免重复包含了,编译器已经解决了重复包含的问题。
若文章对您有帮助,欢迎关注 程序员小迷 。助您在编程路上越走越好!
微风不燥,阳光正好,你就像风一样经过这里,愿你停留的片刻温暖舒心。
我是 程序员小迷 (致力于C、C++、C#、Android、iOS、Java、Kotlin、Objective-C、Swift、Shell、JavaScript、TypeScript、Python等编程技术的技巧经验分享),若作品对您有帮助,请关注、分享、点赞、收藏、在看、喜欢,您的支持是我们为您提供帮助的最大动力。