[其它产品/技术] C语言的include没你想的那么简单

[复制链接]
641|5
 楼主| LOVEEVER 发表于 2023-12-22 14:56 | 显示全部楼层 |阅读模式

你对#include的认识是不是只停留在包含头文件的认知中,好像也没有别的用处,小小东西也翻不起什么风浪?

  1. #include <stdio.h>
  2. #include "user_header.h"

// bala bala
#include就是包含头文件用的,不是吗?!
我之前也一直这么认为的,直到我看了某些大神写的代码,后来我还特意查阅了C99标准。
人家是这么用的
  1. # define DET_START_SEC_VAR_INIT_UNSPECIFIED
  2. # include "MemMap.h"

  3. # define DET_STOP_SEC_VAR_INIT_UNSPECIFIED
  4. # include "MemMap.h"

  5. # define DET_START_SEC_VAR_NOINIT_8BIT
  6. # include "MemMap.h"

  7. # define DET_STOP_SEC_VAR_NOINIT_8BIT
  8. # include "MemMap.h"

还有这样用的
  1. #define STRUCT_GEN_START

  2. #include "defines.h"
  3. #include "param_gen.h"

  4. #include "defines.h"
  5. #include "param_gen.h"

  6. #include "defines.h"
  7. #include "param_gen.h"

  8. #include "defines.h"
  9. #include "param_gen.h"

  10. #include "defines.h"
  11. #include "param_gen.h"

当时,看得我一愣一愣的……
其实,简单来说,#include就是“包含”某个文件的意思,但这个“包含”,不能将思维限死在“头文件”这个概念中,而应该有更多的想象!
 楼主| LOVEEVER 发表于 2023-12-22 15:07 | 显示全部楼层
#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"都是正确的,编译一点问题都没有。


  1. // main.c
  2. #include "add.h"
  3. #include "minus.c"

  4. int add(int a, int b)
  5. {
  6.     return a+b;
  7. }

  8. int main(void)
  9. {
  10.     int c = add(1,2);
  11.     int d = minus(2-1);
  12.     return 0;
  13. }


不妨将脑洞开大一点,除了*.h和*.c文件,我还可以include点别的么?答:可以。例如
  1. // main.c
  2. #include "multiply.txt"

  3. int main(void)
  4. {
  5.     int e = multiply(2,2);
  6.     return 0;
  7. }

甚至,这样也行
  1. // main.c
  2. #include "devide.fxxk"

  3. int main(void)
  4. {
  5.     int f = devide(2,2);
  6.     return 0;
  7. }

继续啊,#include不是放在文件上方,放中间行么。当然
  1. // main.c
  2. int main(void)
  3. {
  4.     #include "squel.xx"
  5.     int g = squel(2,2);
  6.     return 0;
  7. }

好家伙,这么下去,我是不是可以这么干
  1. // data.txt
  2. 1,2,3,4,5,6,7,8,9
  3. // main.c
  4. int arr[] =
  5. {
  6.     #include "data.txt"
  7. }

  8. int main(void)
  9. {
  10.     return 0;
  11. }

然后,你又好奇了,能不能将data.txt换成二进制形式的data.bin?
呵呵,这种不行,编译器在预编译阶段只认得是text文本才行。
好吧……
你不是说这是个预编译指令吗,我很好奇,#include预编译后成啥样子的?
 楼主| LOVEEVER 发表于 2023-12-22 15:09 | 显示全部楼层

这好办,动动手指头,一个gcc -E命令即可搞定。就以上面第一个例子,命令行执行gcc ./main.c -E -o main.i
  1. # 0 ".\\main.c"
  2. # 0 "<built-in>"
  3. # 0 "<命令行>"
  4. # 1 ".\\main.c"

  5. # 1 "add.h" 1
  6. extern int add(int a, int b);
  7. # 3 ".\\main.c" 2
  8. # 1 "minus.c" 1
  9. int minus(int a, int b)
  10. {
  11.     return a-b;
  12. }
  13. # 4 ".\\main.c" 2

  14. int add(int a, int b)
  15. {
  16.     return a+b;
  17. }

  18. int main(void)
  19. {
  20.     int c = add(1,2);
  21.     int d = minus(2-1);
  22.     return 0;
  23. }


看到了吧,#include就是把它后面的文件内容直接include进来。就这么简单粗暴。
那么#include在C语言中是不是很简单?
你说呢!
我见过有人这么写代码的,还TM的一整个团队是这么做的。
将整个所以.h文件全部包含在一个includes.h的头文件中,然后在其他.c文件里面,就直接#include "includes.h"。
  1. // includes.h
  2. #include "adc.h"
  3. #include "uart.h"
  4. #include "spi.h"
  5. #include "iic.h"
  6. #include "dma.h"
  7. #include "pwm.h"
  8. #include "pin.h"
  9. #include "led.h"
  10. #include "os.h"
  11. #include "timer.h"

...
真TM的简便。
我第一次见到这玩意,简直是惊呆了,还有这种操作。

不好吗?有什么不好?多简洁啊!
从上面的分析看,#include就是将它后面包含的头文件源文件,全部展开哦。
简洁?你问过编译器啥感受么?
带来的最直接的感受是,编译过程慢!includes.h里包含得越多就越慢!
另外一个隐含的问题是,会造成include里的内容混乱,头文件里的内容全部是全局的了。
我绝对不推荐这种**的。
因为,预编译还有更好玩的**。
不过,在介绍新**之前,得想个问题,如果一个头文件,重复包含多次会怎样?
也许,你会回答,我是不允许出现这种情况的,就算出现这种情况,我也可以用#ifdef...#endif这种方式规避。
如果你是应届生面试,这样回答,面试官也许是点点头说你有点经验的。
因为重复include,就相当于把头文件重复展开了多次,C语言中有些定义是不允许重复多次的。例如,上面的例子
  1. // main.c
  2. #include "add.h"
  3. #include "minus.c"
  4. #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改成这样就行了
  1. #ifndef _MINUS_
  2. #define _MINUS_
  3. int minus(int a, int b)
  4. {
  5.     return a-b;
  6. }
  7. #endif

这个简单啊,我也会啊。
嗯,但是,我不是想说这个,我真的想说重复include有意想不到的好处呢。
这就不得不提下,我以前写的X-MACRO**了。
 楼主| LOVEEVER 发表于 2023-12-22 15:09 | 显示全部楼层
以下是一个MEMORY字段分配的设想:
将Memory的物理地址映射到自定义逻辑地址

逻辑地址按Memory的Block对齐,逻辑地址从0开始

用户数据按逻辑地址分配

应用接口按实际内容大小操作

底层接口根据逻辑地址对齐读写Memory

我想定义一些内容条目,这些条目分别对应不同的内存地址,不同的长度,以后有需要还可以继续从后面添加就这样:
entry name        address
size
ID_DATA1        0
8
ID_DATA2        8
8
ID_DATA3        16
16
...


可以在一个头文件里面做这样的定义
  1. <p>// defines.h</p><p>#ifdef ENTRY_ID</p><p>  #define ENTRY(id,addr,size) id,</p><p>  #undef ENTRY</p><p>  #undef ENTRY_ID</p><p>#endif</p><p>
  2. </p><p>#ifdef ENTRY_ADDR</p><p>  #define ENTRY(id,addr,size) addr,</p><p>  #undef ENTRY</p><p>  #undef ENTRY_ADDR</p><p>#endif</p><p>
  3. </p><p>#ifdef ENTRY_SIZE</p><p>  #define ENTRY(id,addr,size) size,</p><p>  #undef ENTRY</p><p>  #undef ENTRY_SIZE</p><p>#endif</p>

接着在C文件里面这么玩‍
  1. <p>// memory.c</p><p>#define ALL_ENTRIES()       \</p><p>    ENTRY(ID_DATA1, 0, 8)   \</p><p>    ENTRY(ID_DATA2, 8, 8)   \</p><p>    ENTRY(ID_DATA3, 16, 16) \</p><p>    ENTRY(ID_DATA4, 32, 8)</p><p>
  2. </p><p>#define ENTRY_ID</p><p>#include "defines.h"</p><p>typedef enum</p><p>{</p><p>    ALL_ENTRIES()</p><p>    MEM_ID_MAX</p><p>} MEM_ID;</p><p>
  3. </p><p>#define ENTRY_ADDR</p><p>#include "defines.h"</p><p>const uint32_t mem_addr[] =</p><p>{</p><p>    ALL_ENTRIES()</p><p>};</p><p>
  4. </p><p>#define ENTRY_SIZE</p><p>#include "defines.h"</p><p>const uint16_t mem_size[] =</p><p>{</p><p>    ALL_ENTRIES()</p><p>};</p><p></p>

你也许会反问我,定义一个结构体不就搞定了吗?
别急,这样做的好处是enum的ID顺序跟addr和size是一一对应的,不会错乱,另一个好处是,可以随便在ALL_ENTRIES()下面扩展条目,也不影响ID的对应关系。
如果用结构体去定义的话,也很好,但是会增加数组遍历时间,如果是很庞大的条目数的话,这个效率问题就要考虑了。



两只袜子 发表于 2023-12-22 15:30 | 显示全部楼层
从没觉得很简单
MessageRing 发表于 2023-12-26 10:14 来自手机 | 显示全部楼层
include就是把那个文件内容复制到此处
您需要登录后才可以回帖 登录 | 注册

本版积分规则

353

主题

2803

帖子

7

粉丝
快速回复 在线客服 返回列表 返回顶部