自学内容网 自学内容网

【C语言】编译和链接

目录

一、翻译环境和运行环境

二、翻译环境

(1)预处理(预编译)

(2)编译

① 词法分析

② 语法分析

③ 语义分析

(3)汇编

(4)链接

三、运行环境

四、结语


 

一、翻译环境和运行环境

        任何一个支持 ANSI C 的开发环境,都包含两个环境:

  • 翻译环境:将源代码转换为计算机可执行的二进制指令。
  • 运行环境:执行二进制指令,产生结果。

        如下图所示:

56e2d1457a984d0ea86b9dfb3ab547fb.png

        一个C语言项目可能包含多个 .c 的文件,多个 .c 的文件生成一个可执行文件的过程

  • 多个 .c 文件分别经过编译器,生成对应的目标文件(在 Windows 下,后缀是 .obj;在 Linux 下,后缀是 .o)。
  • 多个目标文件和链接库(C标准函数库和第三方库)一起经过链接器,生成可执行程序。

        过程如下图所示:

dea9873457f846c08cf0e69f818df744.png

        演示:

        项目包含两个源文件:add.c 和 test.c

2a41f0edb6854dc494f5a2eda6211c3b.png

        经过编译器,生成两个目标文件:add.obj 和 test.obj

f1e6cd6e1eb04a9abec2c7c88160cae1.png

        经过链接器,生成一个可执行文件:test_10_5.exe

f04fcbd64dcd4adc83ee3c397c8adb62.png

        也可以查到 cl.exe 和 link.exe:

c7c2c0cec2834c0ba4b477314f236b95.png

90a912667f6340b9bb647faa218371a6.png

        VS 就是一个集成开发环境,包含编辑器、编译器(cl.exe)、链接器(link.exe)、调试器。

二、翻译环境

        翻译环境包含编译和链接两个过程,编译又包含预处理(预编译)、编译和汇编三个过程,结构如下图所示:

e0849bb829e948b4868cbb0107e45ff3.png

       如果把编译这个过程拆分,翻译环境将源程序转为可执行程序这个过程,就变成以下过程:

86e172e68c89488f810da8816c8464f4.png

        因为 VS 是个高度集成的开发环境,它把一些细节隐藏起来了,所以不能看到预处理、编译、汇编这些操作。因此,上面过程的图示,以及下面的演示,都是以 Linux 环境、gcc 编译器为例。不观察细节的演示如下:

        test.c 文件所在目录:

f3bc10536966449b89a2649a2dc0fa1f.png

        test.c 的内容:

bf23ad4d07e747fca6c28ee85d6239b4.png

        翻译 test.c 为可执行文件 test,并运行 test:

69c592c672dc4e61a1f88db79144792f.png

         下面开始讲述细节过程,先把 test.c 的内容改得更丰富一点:

ad7aae799d9c435da4b8512cefa0a593.png

(1)预处理(预编译)

        预处理阶段,会把源文件和头文件处理为 .i  后缀的文件。使用如下命令实现:

3e7beba8781a4d3aa3bdd554623b726d.png

        -E 表示执行完预处理就停止,-o test.i 表示将输出命名为 test.i。查看 test.i 的内容,变成了几百行:

a3e574f55a5b4c29b485bba6e1907b25.png

        预处理阶段,主要处理以#开头的预处理(预编译)指令,比如#include、#define 等,处理规则如下:

  • 删除 #define,并展开宏定义。

        示例中,直接把 N 替换成 100:

bc3ba12aa4ec4ae3ae3b6fa0b04c5deb.png

3ea3e29f4f1b44ec91473768ad82207a.png

  • 处理所有的条件编译指令,如: #if、#ifdef、#elif、#else、#endif 。
  • 处理 #include 预编译指令,将包含的头文件的内容插入到该预编译指令的位置。这个过程是递归进行的,也就是说被包含的头文件也可能包含其他文件。

        示例中,生成的 .i 文件多了很多行,就是因为插入了头文件的内容:

b577ca94d789464cadb6be3f214431f1.png

  • 删除注释。
  • 添加行号和文件名标识,方便后续编译器生成调试信息等。

725d4540629c445eb6f5e1da756d1edd.png

  • 保留所有的 #pragma 编译器指令,编译器后续会使用。

        当我们不知道宏定义或者头文件是否包含正确的时候,可以查看预处理后的 .i 文件确认。

(2)编译

        编译阶段,将预处理后的文件进行:词法分析、语法分析、语义分析及优化,生成汇编代码文件。

        命令如下:

7117c0f0498b4bdb8fc71feb3d462b56.png

        生成的汇编代码如下:

5e1a7c3cb5414ceb969a1a372be7c08e.png

        以下面的代码为例,讲述编译的过程:

array[index] = (index+4)*(2+6);

① 词法分析

        扫描器将源代码中的字符,分割成一系列记号,如:关键字、标识符、字面量、特殊字符等。

        示例代码经过词法分析后,得到16个记号:

记号类型
array标识符
[左方括号
index标识符
]右方括号
=赋值
(左圆括号
index标识符
+加号
4数字
)右圆括号
*乘号
(左圆括号
2数字
+加号
6数字
)右圆括号

② 语法分析

        这一系列记号经过语法分析器产生语法树,语法树是以表达式为节点

        示例代码为例,生成的语法树:

c30dc2abb9f643bb831a690fc3264423.png

        如果代码有语法错误,就不符合表达式规则,也就不能构成一棵语法树。

③ 语义分析

        语义分析器对表达式进行语法层面上的静态语义分析(文本层面上),如:声明和类型的匹配、类型的转换等,该阶段会报告语法错误。

546cf98ff21641d5a537696535001bf1.png

(3)汇编

        汇编器将汇编代码转成机器可执行的指令(二进制指令),就是根据汇编指令和机器指令的对照表一 一进行翻译。

        命令如下(指不指定输出文件名都可以):

2e992bc937a44ebab085f92b227ab230.png

(4)链接

        链接过程,将一堆文件链接在一起,生成可执行程序(在 windows 中就是 test.exe,在 linux中就是 test),主要包括:地址和空间分配,符号决议和重定位等。它解决的是一个项目中多文件、多模块之间互相调用的问题

        命令如下:

61cd31c7b7a0450a8ea6b1dafa36fb00.png

        比如,在一个项目中有 test.c 和 add.c 两个文件:

9b61c558c8084de0a9f3c8ee235e9b1f.png

        test.c 和 add.c 经过编译器,分别生成 test.o 和 add.o,并分别生成各自的符号表。符号表主要记录函数和全局变量的符号,以及它们存储的空间地址。但是在 test.c 中声明的 Add 函数是在 add.c 文件中定义的,因此编译器编译 test.c 文件时,不知道 Add 函数的地址,就把调用 Add 的指令的目标地址暂时搁置。

        链接时,链接器根据 test.o 引用的符号 Add,在 add.o 模块的符号表中查找 Add 函数的地址,然后将 test.o 中所有引用到 Add 的指令重新修正,这个地址修正的过程就叫做重定位

        重定位过程如下图所示,可执行程序 test 只需要查符号表就能执行函数和全局变量:

002c56d52ce44e80b5fdb88c02dceea7.png

        下图因可执行程序查符号表,Add 函数的地址无效,所以发生链接错误:

947ed7217f4549efa0bc1e59749dab26.png

        C 程序更详细的编译和链接过程,可参考《程序员的自我修养》一书。

三、运行环境

        运行可执行程序,产生结果,所依赖的环境就是运行环境。运行步骤如下:

  • 将程序载入内存。

        操作系统载入(操作系统环境中);手工载入(独立运行环境中,如嵌入式开发中烧板子);通过可执行代码载入只读内存。

  • 找到 main 函数对应的二进制指令,作为入口,开始执行程序。
  • 执行程序中,会创建运行时堆栈(stack)即函数栈帧,存储函数的局部变量和返回地址等;同时也可使用静态(static)内存,让其中的变量在整个执行过程一直存在。
  • 程序终止。正常终止 main 函数,或者异常终止。

        在有操作系统的环境中,运行环境就是操作系统。

四、结语

        计算机能识别的是二进制指令,为什么编程时不直接写二进制?

        在早期,科学家们确实是用二进制指令编程,但这样非常不好理解且低效,编程时需要查册子一句句找二进制指令。后来发明了助记符号,每条二进制对应一个助记符,更便于编程,这就是汇编语言。但汇编语言还不够方便,就发明了B语言。最后又在B语言的基础上发明了C语言。B语言和C语言就属于高级计算机语言,易于程序员理解和编程。

139661ec5d6d4d63a6d7cec5ed5f09de.png

        编译器、链接器等等,都是人类发明出的工具,将高级计算机语言自动转成机器能够识别的二进制指令,而不需要程序员理解其中复杂的过程。

 


原文地址:https://blog.csdn.net/2401_86272648/article/details/142711505

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!