代码头文件的使用规则总结和头文件预处理分析

模块化编程对头文件的要求

在模块化编程中,使用头文件是非常常见的做法,头文件通常包含一些常量,宏定义,类型定义,函数声名等信息,可以使代码更加模块化,可读性更高,可维护性更强.

以下是一些模块化编程中应该遵守的偶文件使用规则.

1.只包含需要的头文件,头文件的数量和大小会直接影响编译时间和可读性,为了避免不必要的开销,应该仅包含需要的头文件.

2.不在头文件中定义变量:头文件应该只包含常量,宏定义,类型定义和函数声名等信息.不应该在头文件中定义变量,这回导致变量被多次定义,引发链接错误.

3.避免循环包含,当头文件A包含头文件B,而头文件B也包含头文件A时,就会产生循环包含的情况,这回导致编译错误,不过实际操作中如果完全避免循环依赖需要开发者对头文件的拓扑包含关系有非常清晰的了解,这通常是不可能做到的,所以技术上可以通过GUARD宏来解决头文件循环包含的关系.

4.使用include guard,在头文件中使用include guard可以i避免头文件被多次包含,include guard通常使用宏定义和条件编译实现,可以确保头文件只被包含一次.

5.使用条件编译,在头文件中可以使用条件编译来控制头文件的包含,例如,可以使用#ifdef和#ifndef来避免多次包含同一个头文件.

6.遵循明明规范,头文件的命名应该遵守一定的规范,例如使用小写字母和下划线,或使用大写字母和下划线来分隔单词,这可以使头文件更易于识别和使用.

7.使用标准库头文件,在编写代码时,应该尽可能使用标准库头文件,例如stdio.h,stdlib.h等等,避免使用系统特定的头文件.

8.源代码应该避免引入他们不需要的API声明,如果有,就说明某个头文件耦合过强,或者说出现了大而全的头文件。这种情况下,将大的头文件分解为多个功能独立的小头文件被包含。降低耦合性,提高内聚力,相当于刻板印刷替换为活字印刷。

遵守这些头文件使用规则可以使代码结构更加清晰,可读性更高,可维护性更强,帮助开发者编写高质量的模块化代码.

头文件应该满足自举原则吗?

是的, 头文件应该满足自举原则(self-contained principle),自举原则是指,一个头文件应该能够单独编译和使用,而不需要依赖于其它头文件或者源文件.

如果一个头文件不满足自举原则,那么在包含它的源文件中就需要额外包含其它的头文件,这会导致代码的可读性和可维护性变差.此外,如果头文件包含的内容不清晰或者不充分,还可能引发编译错误或运行时错误.

为了满足自举原则,头文件应该包含它所需要的所有类型定义,宏定义,常量等信息,同时不包含不必要的信息,头文件应该是独立的,可重用的,可以在不同的项目中使用.也可以与其它头文件组合使用.

在实际编程中,我们可以通过以下几个方面来满足自举原则:

1.避免使用其它头文件中的内容,除非这些内容是必要的.

2.将头文件包含在ifndef和endif宏定义中,避免多次重复包含.

3.在头文件中声名变量和函数,而非定义它们,避免重复定义和链接错误.

4.将头文件中的定义和声名按逻辑组织,避免混乱和冗余.

5.遵守命名规范,起有意义的名字,编写高质量代码.

满足头文件自举原则的项目可以使头文件更加清晰,可维护性高,易于使用.

头文件包含逻辑分析

拿linux内核作为分析对象,得到init/main.c源码的预处理文件。

查看main.i文件

第一行表示被预处理的源码文件,也就是main.c,第4行kconfig.h并非是main.c中包含的头文件,而是在命令行中通过-include编译器选项引入进来的,可以通过编译命令

make init/main.i V=1

查看构建细节信息验证,下一行表示第一个1表示././include/linux/kconfig.h的第一行,由于是命令行导入,顺位最大,行数是1, 末尾的1表示新包含文件kconfig.h的开始,查看其内容确实是第一行(不包括guard),包含了autoconf.h文件。

紧接着表示autoconf.h的开始,表示新的autoconfig.h文件的开始,这个文件没有guard宏,也没有引用其它文件。

1 ./include/generated/autoconf.h" 1

所以main.i下一行表示从autoconf.h返回到kconfig.h文件。

后续kconfig.h不包含任何的inlude文件,所以kconfig.h直接清退。开始下一个command line.compiler_types.h,command line也返回,开始进入下一行的compiler_types.h,也是包含在命令行中:

 1 "././include/linux/compiler_types.h" 1 表示compiler_types.h开始。

之后59行包含了一个新的头文件compiler_attributes.h

59 "././include/linux/compiler_types.h"

1 "./include/linux/compiler_attributes.h" 1,表示头文件compiler_attributes.h开始。

头文件compiler_attributes.h中没有有效包含,所以下一行返回到compiler_types.h的60行。

60 "././include/linux/compiler_types.h" 2

之后“68 "././include/linux/compiler_types.h"” compiler_types.h的68行又包含了gcc.h,返回后到69行。

 85 "././include/linux/compiler_types.h"表示compiler_types.h的85行出现了类型定义,将会展开在.i文件中,注意只有类型定义会展开,宏定义不会展开.

最终compiler_types.h结束,返回到最顶层,开始main.c中包含文件的解析:

对比文档中对flags的定义,发现main.i中1,2最多,3 和4 很少,1,2 可以理解,因为它们分别表示包含关系的开始和结束返回,4 表示后续的被当作C外部连接,而3则表示系统头文件,不予警告.

通篇找到1个地方有3,和4的标志:

stdargs.h的引用地方在kernel.h

 /usr/lib/gcc/x86_64-linux-gnu/11/include/stdarg.h位于编译器系统头文件目录,但是在内核的构建配置中,已经将系统头文件用-nostdinc屏蔽了,为何这里又能引用呢?内核在编译的时候是设置为nostdinc的,也就是不包含系统头文件,但是有一个例外,这个例外就是stargs.h头文件目录,他是通过-isystem宏指定的.

至此,疑惑得到了解释。

配置-isystem是在顶层Makefile中设置的:

参考资料

Preprocessor Output (The C Preprocessor)

结束

猜你喜欢

转载自blog.csdn.net/tugouxp/article/details/129834700
今日推荐