C语言中的include很简单,但不是你想象中的那么简单。
你对#include的认识,是不是只停留在包含头文件的认知中,好像也没有别的用处,小小东西也翻不起什么风浪?
#include
#include "user_header.h"
#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语言就是一个大学问了。
但是,我们先不要被这个“
预
编译指令
”名称绕晕。上文,我们提到了头文件这个概念,当然我们也知道还有一个叫源文件的概念。这些我就不解释了。但是,在
C
99标准中有一段这样的话,需要研究下:
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
.
简单地理解,一个source file和一些由#include包含着的headers和source files,通过预编译后,变成一个叫translation unit的东西。
从这里可以看出来,#include不但可以包含headers,还可以包含source files。
所以,我下面这个
#include
"add.h"
和
#include
"minus.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;
}
extern int add(int a, int b);
int minus(int a, int b)
{
return a-b;
}
不妨将脑洞开大一点,除了*.h和*.c文件,我还可以include点别的么?
#include "multiply.txt"
int main(void)
{
int e = multiply(2,2);
return 0;
}
#include "devide.fxxk"
int main(void)
{
int f = devide(2,2);
return 0;
}
继续啊,#include不是放在文件上方,放中间行么?当然可以!
int main(void)
{
#include "squel.xx"
int g = squel(2,2);
return 0;
}
int 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" 1
extern int add(int a, int b);
# 3 ".\\main.c" 2
# 1 "minus.c" 1
int 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进来,就这么简单粗暴。
我见过有人这么写代码的,还TM的一整个团队是这么做的。
将整个所以.h文件全部包含在一个includes.h的头文件中,然后在其他.c文件里面,就直接#include "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"
...
从上面的分析看,#include就是将它后面包含的头文件源文件,全部展开哦。
带来的最直接的感受是,编译过程慢!includes.h里包含得越多就越慢!
另外一个隐含的问题是,会造成include里的内容混乱,头文件里的内容全部是全局的了。
不过,在介绍新玩法之前,得想个问题,如果一个头文件,重复包含多次会怎样?
也许,你会回答,我是不允许出现这种情况的,就算出现这种情况,我也可以用#ifdef...#endif这种方式规避。
如果你是应届生面试,这样回答,面试官也许是点点头说你有点经验的。
因为重复include,就相当于把头文件重复展开了多次,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)
| ^~~~~
#ifndef _MINUS_
#define _MINUS_
int minus(int a, int b)
{
return a-b;
}
#endif
嗯,但是,我不是想说这个,我真的想说重复include有意想不到的好处呢。
-
• 将
M
e
m
o
r
y
的
物
理
地
址
映
射
到
自
定
义
逻
辑
地
址
-
• 逻
辑
地
址
按
M
e
m
o
r
y
的
B
l
o
c
k
对
齐
,
逻
辑
地
址
从
0
开
始
-
• 用
户
数
据
按
逻
辑
地
址
分
配
-
• 应
用
接
口
按
实
际
内
容
大
小
操
作
-
• 底
层
接
口
根
据
逻
辑
地
址
对
齐
读
写
M
e
m
o
r
y
我想定义一些内容条目,这些条目分别对应不同的内存地址,不同的长度,以后有需要还可以继续从后面添加就这样:
entry name
|
address
|
size
|
ID_DATA1
|
0
|
8
|
ID_DATA2
|
8
|
8
|
ID_DATA3
|
16
|
16
|
...
|
|
|
#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