C语言的include没你想的那么简单

c语言中的include很简单,但不是你想象中的简单。 你对#include的认识是不是只停留在包含头文件的认知中,好像也没有别的用处,小小东西也翻不起什么风浪?  
#include #include user_header.h// bala bala#include就是包含头文件用的,不是吗?! 我之前也一直这么认为的,直到我看了某些大神写的代码,后来我还特意查阅了c99标准。 人家是这么用的  # define det_start_sec_var_init_unspecified# include memmap.h  
# define det_stop_sec_var_init_unspecified# include memmap.h
# define det_start_sec_var_noinit_8bit# include memmap.h 
# define det_stop_sec_var_noinit_8bit# include memmap.h 还有这样用的  
#define struct_gen_start  
#include defines.h#include param_gen.h
#include defines.h#include param_gen.h
#include defines.h#include param_gen.h
#include defines.h#include param_gen.h
#include defines.h#include param_gen.h
当时,看得我一愣一愣的…… 其实,简单来说,#include就是“包含”某个文件的意思,但这个“包含”,不能将思维限死在“头文件”这个概念中,而应该有更多的想象! #include在c语言中,算是预编译指令(preprocessing directive)范畴,而预编译指令在c语言就是一个大学问了。 但是,我们先不要被这个“预编译指令”名称绕晕。上文,我们提到了头文件这个概念,当然我们也知道还有一个叫源文件的概念。这些我就不解释了。但是,在c99标准中有一段这样的话,需要研究下:
a source file together with all the headers and source files included via the preprocessing directive #include is known as a preprocessing translation unit. after preprocessing, a preprocessing translation unit is called a translation unit.
iso/iec 9899:1999 (e)
简单地理解,一个source file和一些由#include包含着的headers和source files,通过预编译后,变成一个叫translation unit的东西。 从这里可以看出来,#include不但可以包含headers,还可以包含source files。 所以,我下面这个#include add.h和#include minus.c都是正确的,编译一点问题都没有。  
// main.c#include add.h#include minus.c  
int add(int a, int b){    return a+b;}
int main(void){    int c = add(1,2);    int d = minus(2-1);    return 0;}  
// add.hextern int add(int a, int b); // minus.cint minus(int a, int b){    return a-b;}  不妨将脑洞开大一点,除了*.h和*.c文件,我还可以include点别的么?答:可以。例如  // main.c#include multiply.txt  
int main(void){    int e = multiply(2,2);    return 0;}   甚至,这样也行
// main.c#include devide.fxxk  
int main(void){    int f = devide(2,2);    return 0;}   继续啊,#include不是放在文件上方,放中间行么。当然
// main.cint main(void){    #include squel.xx    int g = squel(2,2);    return 0;}  好家伙,这么下去,我是不是可以这么干
// data.txt1,2,3,4,5,6,7,8,9   // main.cint arr[] = {    #include data.txt}  
int main(void){    return 0;}   然后,你又好奇了,能不能将data.txt换成二进制形式的data.bin? 呵呵,这种不行,编译器在预编译阶段只认得是text文本才行。 好吧…… 你不是说这是个预编译指令吗,我很好奇,#include预编译后成啥样子的? 这好办,动动手指头,一个gcc -e命令即可搞定。就以上面第一个例子,命令行执行gcc ./main.c -e -o main.i  
# 0 .\main.c# 0 # 0 # 1 .\main.c  
# 1 add.h 1extern int add(int a, int b);# 3 .\main.c 2# 1 minus.c 1int minus(int a, int b){    return a-b;}# 4 .\main.c 2
int add(int a, int b){    return a+b;}
int main(void){    int c = add(1,2);    int d = minus(2-1);    return 0;}
  看到了吧,#include就是把它后面的文件内容直接include进来。就这么简单粗暴。 那么#include在c语言中是不是很简单? 你说呢! 我见过有人这么写代码的,还tm的一整个团队是这么做的。 将整个所以.h文件全部包含在一个includes.h的头文件中,然后在其他.c文件里面,就直接#include includes.h。
// includes.h#include adc.h#include uart.h#include spi.h#include iic.h#include dma.h#include pwm.h#include pin.h#include led.h#include os.h#include timer.h...  真tm的简便。 我第一次见到这玩意,简直是惊呆了,还有这种操作。 不好吗?有什么不好?多简洁啊! 从上面的分析看,#include就是将它后面包含的头文件源文件,全部展开哦。 简洁?你问过编译器啥感受么? 带来的最直接的感受是,编译过程慢!includes.h里包含得越多就越慢! 另外一个隐含的问题是,会造成include里的内容混乱,头文件里的内容全部是全局的了。 我绝对不推荐这种玩法的。 因为,预编译还有更好玩的玩法。 不过,在介绍新玩法之前,得想个问题,如果一个头文件,重复包含多次会怎样? 也许,你会回答,我是不允许出现这种情况的,就算出现这种情况,我也可以用#ifdef...#endif这种方式规避。 如果你是应届生面试,这样回答,面试官也许是点点头说你有点经验的。 因为重复include,就相当于把头文件重复展开了多次,c语言中有些定义是不允许重复多次的。例如,上面的例子  // main.c#include add.h#include minus.c#include minus.c  
  这样是有问题的,因为上面相当于重复定义了两次int minus(int a, int b)函数了。
in file included from .main.c:4:minus.c:1:5: 错误:‘minus’重定义    1 | int minus(int a, int b)      |     ^~~~~  如果将minus.c改成这样就行了
#ifndef _minus_#define _minus_int minus(int a, int b){    return a-b;}#endif  这个简单啊,我也会啊。 嗯,但是,我不是想说这个,我真的想说重复include有意想不到的好处呢。 这就不得不提下,我以前写的x-macro大法了。 以下是一个memory字段分配的设想:  
将memory的物理地址映射到自定义逻辑地址
逻辑地址按memory的block对齐,逻辑地址从0开始
用户数据按逻辑地址分配
应用接口按实际内容大小操作
底层接口根据逻辑地址对齐读写memory
我想定义一些内容条目,这些条目分别对应不同的内存地址,不同的长度,以后有需要还可以继续从后面添加就这样:
entry name address size
id_data1 0 8
id_data2 8 8
id_data3 16 16
...
可以在一个头文件里面做这样的定义
// defines.h#ifdef entry_id  #define entry(id,addr,size) id,  #undef entry  #undef entry_id#endif  
#ifdef entry_addr  #define entry(id,addr,size) addr,  #undef entry  #undef entry_addr#endif
#ifdef entry_size  #define entry(id,addr,size) size,  #undef entry  #undef entry_size#endif   接着在c文件里面这么玩‍  
// memory.c#define all_entries()          entry(id_data1, 0, 8)      entry(id_data2, 8, 8)      entry(id_data3, 16, 16)    entry(id_data4, 32, 8)  
#define entry_id#include defines.htypedef enum{    all_entries()    mem_id_max} mem_id;
#define entry_addr#include defines.hconst uint32_t mem_addr[] ={    all_entries()};
#define entry_size#include defines.hconst uint16_t mem_size[] ={    all_entries()};
  你也许会反问我,定义一个结构体不就搞定了吗? 别急,这样做的好处是enum的id顺序跟addr和size是一一对应的,不会错乱,另一个好处是,可以随便在all_entries()下面扩展条目,也不影响id的对应关系。 如果用结构体去定义的话,也很好,但是会增加数组遍历时间,如果是很庞大的条目数的话,这个效率问题就要考虑了。 其实,对上面的做法,我还做了优化,写在了这两篇文章中,x-macro是个很酷的玩法哦,欢迎查阅和讨论。


阐明Pt单原子催化剂的轴向配体效应对碱性析氢反应的影响
金升阳连续四年荣登广东省制造业500强
天骏科技数字档案管理系统V1.0获鲲鹏 Validated 认证
智慧城市能否在新政策下创新
PS-5306多工位转轴试验机的介绍
C语言的include没你想的那么简单
vivoNEX全屏幕发声技术是什么
YCbCr/YPbPr色差分量串口ESD静电保护推荐TVS二极管型号
AMD7nmRadeonVII显卡评测 分辨率越高表现越好
车联网2.0时代 车载OS成BAT竞争焦点
华为、浪潮力推GaN,万亿级市场打开
可编程控制器与变频器的连接和连接时应注意的问题
运放稳定性,你真的懂了吗?
AI渗透到动物保护领域 安防技术撑起动物保护伞
CY8C22x45系列PSoC芯片实现触摸感应系统的设计
LG电子将新推出3款OLED显示器
即将发布!iQOO Neo 5获得3C认证
Analog On Top的SOC如何做Top设计?
病害肉快速分析仪的应用有效保障了人们的饮食安全
Infineon的轮胎压监控系统(TPMS)解决方案