可能大家都知道一个C语言程序需要经过编译生成可执行文件就可以运行起来,但是这并非是一个完整的C语言程序流程,下面我们就详细了解一下C语言程序的整个生命周期。

一个完整C语言的生命周期分为以下五个部分:

  • 编写代码
  • 编译
  • 链接
  • 装载
  • 执行

1. 编写代码

编写代码是大家最熟悉不过的了,相信大家在学校里学C语言的第一步都是输出"Hello World",也就是编写代码这一步。如果大家使用的开发环境是Visual Studio等IDE的话,直接点个运行,程序的结果就输出在你面前了。如果不详细追究过程,可能连编译是什么都不知道(初学C语言时的我就是这样的),以为编写的代码本来就是可以直接运行的,这是因为IDE(集成开发环境)把编译、链接、装载、执行都集成到运行按钮上了,IDE可以简化开发流程,对新手比较友好,但是不利于我们理解C语言程序生命周期。下面我们就详细了解一下编译、链接、装载、执行这些步骤。

2. 编译

大家都知道计算机只能识别0和1,所以编译就是将高级语言写的代码转换成计算机能够识别的二进制即0和1,如果在学习C语言的时候用的是Linux操作系统,那么应该了解编译这一过程,比如使用命令gcc main.c -o main即可将一个名为main.c的程序编译为名为main.o的二进制可执行文件。

上述命令中的gcc为C语言编译器,编译器负责编译程序。除gcc编译器以外还有许多编译器,比如负责交叉编译的gcc-arm,clang等编译器。

编译器的输入是程序源文件,比如上述命令中的main.c就是一个C语言源文件。同时编译器可以有多个输入,比如下列命令gcc main.c function.c -o main,可以将多个有关联的文件组合为一个main.o程序。当然通常情况下,不会这么编译程序,而是借助makefile。

编译器的输出是所有二进制目标文件的集合,每一个目标文件对应一个输入文件,最后为了让程序运行起来,还需要经过链接这一过程,即将所有目标文件组合为一个可执行文件的过程。

编译分为如下4个步骤:详细过程可以参考我之前写的帖子认识GCC_CyberMakes的博客-CSDN博客

a. 预处理(Preprocessing):对每个源文件进行预处理,包括宏展开、头文件包含、条件编译等。预处理的输出是扩展后的源文件。

b. 编译(Compiling):将预处理后的源文件编译成汇编代码。编译器将源代码转换为特定机器架构的汇编语言。

c. 汇编(Assembling):将汇编代码转换为机器代码,生成目标文件。汇编器将汇编语言转换为机器指令和数据。

d. 链接(Linking):链接器将所有的目标文件和库文件合并,生成最终的可执行文件。

其实链接也是编译的一部分,但是需要单独看一下,因为链接过程比较重要。

3. 链接

链接是将多个目标文件和库文件组合成一个可执行文件或者共享库的过程。链接器将目标代码与所需的库函数和其他目标文件进行组合,生成最终的可执行文件。链接分为静态链接和动态链接两种类型,动态库和静态库也是两个比较重要的概念,具体可以参考《高级C/C++编译技术》这本书。

在单片机开发中主要以静态链接为主,因为单片机无法进行动态链接,所有库文件必须在编译阶段完成链接。

  1. 符号解析(Symbol resolution):链接器解析目标文件中使用的符号,包括函数、变量等。对于未定义的符号,链接器会在其他目标文件和库文件中查找定义。

  2. 重定位(Relocation):在符号解析之后,链接器需要确定每个符号在最终可执行文件中的地址。它会将每个符号的引用替换为对应的地址。

  3. 符号表生成(Symbol table generation):链接器生成一个符号表,记录所有的符号及其对应的地址。这个符号表在程序执行时,可用于动态链接、调试等。

  4. 地址解析(Address resolution):如果目标文件或库文件中有引用其他库的符号,链接器会解析这些引用,并将它们与相应的库文件关联起来。

  5. 可执行文件生成(Executable file generation):最后,链接器将经过重定位和符号解析后的目标文件和库文件合并成一个可执行文件。这个可执行文件包含了所有需要的代码和数据,可以直接执行。

待补充

4. 装载

待补充

5. 执行

待补充

07-01 12:53