-I <头文件PATH>一定会最先搜索吗?头文件路径搜索顺序五花八门,有没有规律?搜索路径?如何得到编译器预处理头文件搜索路径?为什么C语言头文件路径不用遵循转义字符规则?

目录

-I <头文件PATH>一定会最先搜索吗?

头文件路径搜索顺序五花八门,有没有规律?

头文件搜索顺序规则

覆盖系统头文件

搜索路径

GCC头文件

MSVC头文件

GCC库文件

系统头文件路径

为什么会有头文件循环依赖?

头文件并不是编译单元?

如何得到编译器预处理头文件搜索路径?

为什么C语言头文件路径不用遵循转义字符规则?

如何包含头文件最多只有一次?


-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等编程技术的技巧经验分享),若作品对您有帮助,请关注、分享、点赞、收藏、在看、喜欢,您的支持是我们为您提供帮助的最大动力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值