0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

C语言的include没你想的那么简单(图文版)

嵌入式软件实战派 来源:嵌入式软件实战派 2023-06-08 16:44 次阅读
C语言中的include很简单,但不是你想象中的简单。(如果你不想看以下文字内容,可以查看本文对应的视频版) 你对#include的认识是不是只停留在包含头文件的认知中,好像也没有别的用处,小小东西也翻不起什么风浪?
#include 
#include"user_header.h"
// bala bala
#include就是包含头文件用的,不是吗?! 我之前也一直这么认为的,直到我看了某些大神写的代码,后来我还特意查阅了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"
还有这样用的
#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"
当时,看得我一愣一愣的…… 其实,简单来说,#include就是“包含”某个文件的意思,但这个“”,不能将思维限死在“头文件”这个概念中,而应该有更多的想象!#include在C语言中,算是预编译指令(preprocessing directive)范畴,而预编译指令在C语言就是一个大学问了。但是,我们先不要被这个“编译指令”名称绕晕。上文,我们提到了头文件这个概念,当然我们也知道还有一个叫源文件的概念。这些我就不解释了。但是,在C99标准中有一段这样的话,需要研究下:

Asourcefiletogetherwithalltheheadersandsourcefilesincludedviathepreprocessingdirective#includeisknownasapreprocessingtranslationunit.Afterpreprocessing,apreprocessingtranslationunitiscalledatranslationunit.

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.h
externintadd(inta,intb);
//minus.c
int minus(int a, int b)
{
    return a-b;
}
不妨将脑洞开大一点,除了*.h和*.c文件,我还可以include点别的么?答:可以。例如
//main.c
#include"multiply.txt"


int main(void)
{
inte=multiply(2,2);
    return 0;
}
甚至,这样也行
//main.c
#include"devide.fxxk"


int main(void)
{
intf=devide(2,2);
    return 0;
}
继续啊,#include不是放在文件上方,放中间行么。当然
//main.c
int main(void)
{
#include"squel.xx"
intg=squel(2,2);
    return 0;
}
好家伙,这么下去,我是不是可以这么干
//data.txt
1,2,3,4,5,6,7,8,9
//main.c
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进来。就这么简单粗暴。那么#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字段分配的设想:
  1. Memory

  2. MemoryBlock0

  3. 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的对应关系。如果用结构体去定义的话,也很好,但是会增加数组遍历时间,如果是很庞大的条目数的话,这个效率问题就要考虑了。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • C语言
    +关注

    关注

    180

    文章

    7530

    浏览量

    128694
  • 代码
    +关注

    关注

    30

    文章

    4555

    浏览量

    66769

原文标题:C语言的include没你想的那么简单(图文版)

文章出处:【微信号:embedded_sw,微信公众号:嵌入式软件实战派】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    简单易懂的C语言电子书教程

    简单易懂的C语言电子书教程 C语言的发展过程  C语言是在70年代初问世的。一九七八年由美国电话电报公司(AT&T)贝尔实
    发表于 12-08 10:49

    简单C语言架构,很简单哦 !!!

    简单C语言架构,很简单哦。。。。{:4_95:}
    发表于 04-04 20:03

    简单C语言

    简单C语言简单C语言简单
    发表于 07-14 20:12

    看了那么c语言书籍,还是这本好!

    看了那么c语言书籍,还是这本好!http://pan.baidu.com/share/link?shareid=1571838976&uk=2620168565&fid=519007397
    发表于 03-22 09:30

    怎样学习C语言

    是程序设计的重要基础。还有啊,多说一点:即使现在招聘程序员,考试都是考C语言你想加入it行业,那么就一定要掌握好C
    发表于 04-28 16:52

    关于单片机C语言include和proteus

    AT89C51和AT89C52单片机C语言#include中是写reg51.h还是reg52.hproteus中10后边的是输入uF还是μF
    发表于 03-01 19:57

    如何理解C语言include

    编写C程序时,include后面有的跟 ,有的跟""。表示编译器先在系统目录下查找头文件,再在工程指定目录下查找头文件;""表示现在工程指定目录下查找头文件,再在系统目录下查找头文件。这样理解对吗?
    发表于 10-31 01:20

    详细科普:开关电源你想那么简单

    。 ②输入滤波电路:C1、L1、C2、C3 组成的双π型滤波网络主要是对输入电源的电磁噪声及杂波信号进行抑制,防止对电源干扰,同时也防止电源本身产生的高频杂波对电网干扰。当电源开启瞬间,要对
    发表于 12-11 17:09

    C语言是怎么变成汇编的

    他们之间的对应关系。作为作业今天就在这里写下我的发现吧。按照要求这是一段很简单c语言程序进行编译生成汇编文件生成的main.s文件 已经把多余的命令删除了(gvim好像确实vim好
    发表于 07-14 08:01

    C语言基础怎么学51单片机呢

    C语言基础怎么学51单片机呢?为什么学好C语言却不会弄单片机呢 ?
    发表于 10-18 09:17

    简单信号发生器源程序C语言代码

    简单信号发生器源程序C语言代码 #include #include #include #define DA0832 XBYTE[0Xa00
    发表于 04-07 08:32 2884次阅读

    Verilog HDL语言的文件调用问题:include使用方法介绍

    本文简单介绍在使用Verilog HDL语言时文件的调用问题之include使用方法介绍及举例说明,详见本文...
    发表于 01-24 14:40 6461次阅读
    Verilog HDL<b class='flag-5'>语言</b>的文件调用问题:<b class='flag-5'>include</b>使用方法介绍

    简单介绍图文分析报告的制作方法

    图文分析报告是一种图文并茂的数据可视化分析报表,由数据可视化图表和文字解说部分共同组成。即使不看图表,用户也能通过解说文字迅速掌握关键数据信息。那么图文分析报告难做吗?怎么做?下文将
    发表于 10-09 11:37 7304次阅读

    C语言include你想那么简单

    C语言中的include简单,但不是你想象中的简单。 你对#include的认识是不是只停留在
    发表于 07-06 09:30 183次阅读

    C语言中的include有什么用

    C语言中的include简单,但不是你想象中的简单。 你对#include的认识是不是只停留在
    发表于 07-06 09:30 699次阅读