C语言#include头文件真的是插入代码吗?头文件并不是编译单元?为什么会有头文件循环依赖?如何得到编译器预处理头文件搜索路径?如何得到编译器预处理搜索到的头文件位置?源代码一定需要包含头文件吗?

C语言#include头文件真的是插入代码吗?

编译器理论和实作

既是又不是。

  • 从编译器理论理解,#include头文件"相当于"插入了头文件的代码,以供源代码引用(宏定义、函数声明、其他头文件引入等等),这仅仅是理论的效果。利用GCC -E或者MSVC /E或/P选项可以看到"插入"的效果。
  • 从编译器实作角度,为了提高效率,编译器在编译源代码的时候并不会将#include头文件直接插入到当前编译的源代码,一则会产生代码插入,源代码所在的内存移动,二则不利于相同头文件的优化。主流编译器的做法是单独申请内存保存文件并记录#include行号以便处理完头文件后继续编译当前源代码。

引用大量头文件造成编译耗时灾难

  • 如果你看过windows.h或者看过C/C++/ObjC大工程,尤其源代码开头一堆头文件,修改某一个base头文件造成大量源代码被编译,鉴于此,C++20引入modules减少头文件包含编译时间,虽然Java/C#/Python早就抛弃头文件采用import module.
  • 预编译头文件以及头文件缓存都是头文件优化策略。
  • VS .pch和GCC的.gch都属于预编译头文件,注意一般选择稳定不会被经常改动的头文件。
  • 确认预编译头文件真的加速编译过程,在VS选项中可开启"生成计时"可查看编译过程各个工具消耗的时间。
    • 生成计时: "工具" -> "选项" -> "项目和解决方案" -> "VC++项目设置" -> "生成计时"

头文件并不是编译单元?

经常写C/C++代码的人,会注意到,编译器提示的日志基本不会包含.h的编译,除非是.h有报错才会提示.h的讯息。这是因为,C/C++的编译单元是源代码.c或.cpp, 而非.h. .h只是辅助.c/.cpp而已,给编译器看,编译器根本没把.h当人。

  • 编译单元(Compilation Unit)是编程语言和编译器中的一个概念,它指的是源代码文件在编译过程中被编译器处理的最小单位。
  • 大部分编程语言的编译单元都是源代码文件。

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

有头文件的编程语言体系中,头文件的本意是给其他文件提供基本的类型、声明或者宏等讯息供参考,让编译器知晓这些类型讯息。一般的原则是,越是common的头文件会被其他头文件依赖,即xxx.h很可能会包含common.h. 但定义common以及不同头文件的依赖很可能陷入一个困境,大家都想要对方头文件的讯息,这就出现了头文件循环依赖。

  • 一种解法是更好地规划头文件。
  • 另外一种解法,C/ObjC/C++都提供前向声明解决循环依赖。例如C语言的struct xxx, ObjC的@class xxx, C++的class xxx.

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

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.

如何得到编译器预处理搜索到的头文件位置?

gcc可以通过-MD命令获取编译的头文件依赖关系。例如gcc -MD demo.c可以得到类似如下:

demo.o: demo.c /usr/include/stdc-predef.h /usr/include/stdio.h \

 /usr/include/x86_64-linux-gnu/bits/libc-header-start.h \

 /usr/include/features.h /usr/include/features-time64.h \

 /usr/include/x86_64-linux-gnu/bits/wordsize.h \

 /usr/include/x86_64-linux-gnu/bits/timesize.h \

......

源代码一定需要包含头文件吗?

头文件并非一定需要包含进来,而是有需要的时候包含进来,以让编译器了解类型声明或宏代替。

  • 例如,int main() { return 0; } 不需要包含任何头文件即可编译,所有词法token默认都可由编译器正确解析。
  • 即使是用到C库函数,也不一定要包含stdxxx.h, 在代码开始增加它的声明即可。链接器知道从libc中找到对应函数实现即可。
  • Go语言用import引入库,如果没有使用库,也可以没有import语句。比如使用print而不是fmt.Println, 不需要import "fmt".

声明包

  • Go语言一定需要声明所属的包,即package xxx语句一定需要出现在源代码开始。


若文章对您有帮助,欢迎关注 程序员小迷 。助您在编程路上越走越好!

微风不燥,阳光正好,你就像风一样经过这里,愿你停留的片刻温暖舒心。

我是 程序员小迷 (致力于C、C++、C#、Android、iOS、Java、Kotlin、Objective-C、Swift、Shell、JavaScript、TypeScript、Python等编程技术的技巧经验分享),若作品对您有帮助,请关注、分享、点赞、收藏、在看、喜欢,您的支持是我们为您提供帮助的最大动力。

你可能感兴趣的:(小话c语言,编程语言,编译器,c语言,c++,visual,studio,msvc,gcc,clang,头文件)