c语言自定义头文件_c语言编译过程

2022-09-11 10:37:14

8372e60aa82b124c3006f8c376be9fe6.gif


这篇思考下C语言的编译过程,通过编译过程再看其它,也许会有不一样的理解。

参与过程的结果,才会对结果有不一样的感觉。只追求结果,也许会得出“理所当然”的错觉,自己深陷其中,还浑然不觉。太多所谓的“客观”,到头来还只是个主观的理解,如此而已!


学习c语言的第一节课,往往第一个接触的代码便是如图1所示的代码 :

#include int main(){   printf("hello world!\n");   return 0;}

图1 第一个c语言代码

记得每接触一个新的语言,教你的第一次可运行代码都是打印“hello world”。每次想为什么要打印这句话呢?到头来突然发现,这是欢迎你进入“程序的世界”,就像一个刚诞生的生命一样,刚来到这个世界,当然要“hello world”了。另一层含义就是:来到这个“编程”世界,希望你能一如既往的保持一颗好奇的心,坚持到底,毕竟编程的路上真的是“以苦作舟”啊。

借助这个程序想第一步谈下这个程序编译的过程,从总体了解下输出一个“hello world”为什么需要写上面的一系列代码。通过总体了解,然后再详细看c语言的世界,也许有更多点思考和理解。

从一个源程序(也就是你用IDE编辑的文件,以.c结尾的那个文件)到真正可运行的程序,这个过程到底经历了什么呢?看图2所示:

3b4901e2299891e7a4e4bbff973cc3e1.png

图2 c程序编译过程

图2给出的是一个源程序到一个可执行程序大体经历的几个阶段,当然每个阶段细究下去还有很多东西,这里先从总体上谈讨论下。

第一步:编辑c的源程序,也就是用IDE(例如devc++)编写一个以.c结尾的源程序。如图1所示的代码。

第二步:预处理

预处理是在编译之前进行的很关键的一步。预处理实质上是处理以"#"开头的文本(这里说文本而不是说语句,是因为这里是没有分号,不是语句,但凡语句是在编译阶段进行处理的呢!),将#include包含的头文件直接拷贝到.c源文件当中;将#define定义的宏进行替换,同时将代码中没用的注释部分删除等。

具体做的事儿大体有以下几部分:将所有的#define删除,并且展开所有的宏定义。说白了就是字符替换(因为是对整个文件进行扫描并进行替换,也就是从总体上对文件进行替换,所以叫“宏替换”,宏其实就是总体的意思呢。);处理所有的条件编译指令,#ifdef #ifndef #endif等,就是带#的那些;处理#include,将#include指向的文件插入到该行处,这里就是将所有的头文件所指向文件中的内容copy一份将其复制到当前文件中,让真正编译的时候能够找到想找的东西;删除所有注释;添加行号和标示,这样的在调试和编译出错的时候才知道是是哪个文件的哪一行。

#include又叫头文件,分为标准库头文件和自定义头文件。引入标准库头文件的语法是

#include // 引入标准库头文件#include "my.h" // 引入自定义头文件

路上,引入标准库头文件是用两个<>(尖括号),引入自定义头文件用的是两个双引号。双引号"xxx.h",表示编译器先在用户的工作目录下搜索头文件,如果搜索不到则到系统默认目录下去寻找,所以双引号一般用于包含用户自己编写的头文件。如:#include "my.h"。尖括号,表示编译器只在系统默认目录或尖括号内的工作目录下搜索头文件,并不去用户的工作目录下寻找,所以一般尖括号用于包含标准库文件,如:#include 。

为了清晰,接下来用devc++编写第一个程序test.c。如图3所示:

8d8f9662dfad5fa8f513ed8038c28932.png

图3 devc++编写第一个c语言程序

接下来用gcc  -E test.c -o a.c生成预编译后的文件a.c。打开a.c会发现#include 被这个头文件中的内容替换了!现截取其中一部分如图4所示:

2e06289b13e11da169afe15ea4216503.png

图4 预编译后的文件

第三步:编译

编译的过程实质上是把高级语言翻译成机器语言的过程。学过编译原理的话,都知道编译原理有什么词法分析、语法分析、语义分析及优化等等,这些过程都是在编译阶段完成的。先直接给出编译后的代码,咱们可以用gcc -S test.c -o a.s这个命令生成编译后的代码。如图5所示:

6c89f205adc88ca8f3e9bce10e00900f.png

图5 test.c编译后的代码

这段汇编代码不过多解释,先大体了解。先明白总体过程。这里是将源代码翻译成汇编代码,接下来可以通过如下命令gcc -c test.c -o a.o进一步将源代码翻译成二进制代码(此时的二进制代码只是中间代码!)

接下来看

第四步:链接

就像刚才的hello.c它使用到了C标准库的东西“printf”,但是编译过程只是把源文件翻译成二进制而已,这个二进制还不能直接执行,这个时候就需要做一个动作,将翻译成的二进制与需要用到库绑定在一块。编译过程靠预编译过程头文件的声明只是知道printf这样的函数已经准备好了,但此时并不知道到底到哪去找到他,链接阶段就是实实在在找打printf的具体位置,需要的时候就可以直接拿来用了。gcc test.c -o a可以生成可执行程序。即gcc不带任何参数。此时链接器会根据配置的库文件路径自动去寻找需要的库文件并确定需要函数的地址,此时就是将编译后代码中的符号地址真正的转换成逻辑地址的过程,有了逻辑地址程序在执行的时候就可以真正找到所需“工具”的物理地址了,程序就可以运行起来了。
d28d9dbbe844f2c09ffe26fe0032dfcb.pngEND
  • 作者:weixin_39677538
  • 原文链接:https://blog.csdn.net/weixin_39677538/article/details/111211148
    更新时间:2022-09-11 10:37:14