C语言的include没你想的那么简单!
来源:玩点嵌入式发布时间:2023-07-061965浏览
询问 AIC语言中的include很简单,但不是你想象中的简单。你对#include的认识是不是只停留在包含头文件的认知中,好像也没有别的用处,小小东西也翻不起什么风浪?
我之前也一直这么认为的,直到我看了某些大神写的代码,后来我还特意查阅了C99标准。
人家是这么用的
所以,我下面这个#include "add.h"和#include "minus.c"都是正确的,编译一点问题都没有。
不妨将脑洞开大一点,除了*.h和*.c文件,我还可以include点别的么?答:可以。例如
甚至,这样也行
继续啊,#include不是放在文件上方,放中间行么。当然
好家伙,这么下去,我是不是可以这么干
然后,你又好奇了,能不能将data.txt换成二进制形式的data.bin?
呵呵,这种不行,编译器在预编译阶段只认得是text文本才行。
好吧……你不是说这是个预编译指令吗,我很好奇,#include预编译后成啥样子的?
这好办,动动手指头,一个gcc -E命令即可搞定。就以上面第一个例子,命令行执行gcc ./main.c -E -o main.i
看到了吧,#include就是把它后面的文件内容直接include进来。就这么简单粗暴。
那么#include在C语言中是不是很简单?
你说呢!
我见过有人这么写代码的,还TM的一整个团队是这么做的。
将整个所以.h文件全部包含在一个includes.h的头文件中,然后在其他.c文件里面,就直接#include "includes.h"。
真TM的简便。我第一次见到这玩意,简直是惊呆了,还有这种操作。
不好吗?有什么不好?多简洁啊!
从上面的分析看,#include就是将它后面包含的头文件源文件,全部展开哦。
简洁?你问过编译器啥感受么?
带来的最直接的感受是,编译过程慢!includes.h里包含得越多就越慢!
另外一个隐含的问题是,会造成include里的内容混乱,头文件里的内容全部是全局的了。我绝对不推荐这种玩法的。
因为,预编译还有更好玩的玩法。不过,在介绍新玩法之前,得想个问题,如果一个头文件,重复包含多次会怎样?
也许,你会回答,我是不允许出现这种情况的,就算出现这种情况,我也可以用#ifdef...#endif这种方式规避。
如果你是应届生面试,这样回答,面试官也许是点点头说你有点经验的。
因为重复include,就相当于把头文件重复展开了多次,C语言中有些定义是不允许重复多次的。例如,上面的例子
如果将minus.c改成这样就行了
这个简单啊,我也会啊。
嗯,但是,我不是想说这个,我真的想说重复include有意想不到的好处呢。
这就不得不提下,我以前写的X-MACRO大法了。以下是一个MEMORY字段分配的设想:
可以在一个头文件里面做这样的定义
接着在C文件里面这么玩
你也许会反问我,定义一个结构体不就搞定了吗?别急,这样做的好处是enum的ID顺序跟addr和size是一一对应的,不会错乱,另一个好处是,可以随便在ALL_ENTRIES()下面扩展条目,也不影响ID的对应关系。如果用结构体去定义的话,也很好,但是会增加数组遍历时间,如果是很庞大的条目数的话,这个效率问题就要考虑了。其实,对上面的做法,我还做了优化,写在了这两篇文章中,X-MACRO是个很酷的玩法哦,欢迎查阅和讨论。声明:
本文转载自嵌入式软件实战派公众号,如涉及作品内容、版权和其它问题,请于联系工作人员微(prrox66),我们将在第一时间和您对接删除处理!

#include就是包含头文件用的,不是吗?!#include <stdio.h>#include"user_header.h"// bala bala
我之前也一直这么认为的,直到我看了某些大神写的代码,后来我还特意查阅了C99标准。
人家是这么用的
还有这样用的#defineDET_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"
当时,看得我一愣一愣的……其实,简单来说,#include就是“包含”某个文件的意思,但这个“包含”,不能将思维限死在“头文件”这个概念中,而应该有更多的想象!#include在C语言中,算是预编译指令(preprocessing directive)范畴,而预编译指令在C语言就是一个大学问了。但是,我们先不要被这个“预编译指令”名称绕晕。上文,我们提到了头文件这个概念,当然我们也知道还有一个叫源文件的概念。这些我就不解释了。但是,在C99标准中有一段这样的话,需要研究下:#defineSTRUCT_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"
简单地理解,一个source file和一些由#include包含着的headers和source files,通过预编译后,变成一个叫translation unit的东西。从这里可以看出来,#include不但可以包含headers,还可以包含source files。Asource filetogether with all the headers and source files included via the preprocessing directive#includeis known as a preprocessing translation unit.After preprocessing,a preprocessing translation unit is called atranslation unit.
ISO/IEC 9899:1999 (E)
所以,我下面这个#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.hexternintadd(inta,intb);
//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 "<built-in>"# 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语言中有些定义是不允许重复多次的。例如,上面的例子
这样是有问题的,因为上面相当于重复定义了两次int minus(int a, int b)函数了。// main.c#include "add.h"#include "minus.c"#include "minus.c"
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.h"typedef enum{ALL_ENTRIES()MEM_ID_MAX} MEM_ID;
#define ENTRY_ADDR#include "defines.h"const uint32_t mem_addr[] ={ALL_ENTRIES()};
#define ENTRY_SIZE#include "defines.h"const uint16_t mem_size[] ={ALL_ENTRIES()};
你也许会反问我,定义一个结构体不就搞定了吗?别急,这样做的好处是enum的ID顺序跟addr和size是一一对应的,不会错乱,另一个好处是,可以随便在ALL_ENTRIES()下面扩展条目,也不影响ID的对应关系。如果用结构体去定义的话,也很好,但是会增加数组遍历时间,如果是很庞大的条目数的话,这个效率问题就要考虑了。其实,对上面的做法,我还做了优化,写在了这两篇文章中,X-MACRO是个很酷的玩法哦,欢迎查阅和讨论。声明:
本文转载自嵌入式软件实战派公众号,如涉及作品内容、版权和其它问题,请于联系工作人员微(prrox66),我们将在第一时间和您对接删除处理!

扫码添加客服微信,备注“入群”拉您进凡亿教育官方专属技术微信群,与众位电子技术大神一起交流技术问题及心得~
分享💬点赞👍在看❤️“三连”支持!新闻来源:玩点嵌入式,文中所述为作者独立观点,不代表icspec立场。更多精彩资讯请下载icspec App。如对本稿件有异议,请联系微信客服specltkj。

