序言
:
在编写C语言程序中,想要程序运行起来源程序一定会经历编译+链接
这两个步骤,本章内容将具体讨论这两个步骤分别是是什么,做了哪些事
程序的翻译和执行环境
在ANSIC
的任何一种实现中,存在两个不同的环境
- 翻译环境,在翻译环境中将源代码转换为可执行的
机器指令
- 执行环境,用于实际执行代码
注意:翻译环境中生成的机器指令是二进制的指令,是可以直接运行的
翻译环境
编译+链接
我们通常所说的编译、链接
究竟是在哪里执行的呢?
编译、链接是在翻译环境中进行的,每一个源文件都会单独被编译器编译称为目标文件(.o/.obj),最后所有的目标文件通过连接器
链接称为可执行程序(.exe)
每个源文件是单独被编译的
- 组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
- 每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
- 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程
因此翻译环境中有2个工具----->
编译器、链接器
在vs的环境中,编译器为cl.exe
,链接器为link.exe
下面使用gcc编译器
来演示程序在翻译环境中所经历的具体过程
翻译环境具体的过程
预处理
预处理命令gcc -E test.c -o test.i
预处理完成之后就停下来,预处理之后产生的结果都放在test.i
文件中
预处理展开的头文件和标准头文件代码不完全一样,说明预处理展开时对头文件进行了部分更改
结论1:预处理会进行头文件的包含#include
结论2:预处理会忽略注释,并且替换#define
定义的标识符
编译
编译命令gcc -S test.c
编译完之后就停下来,结果保存在test.s
中
结论:编译过程中将源程序翻译为汇编代码
编译阶段还会进行
- 语法分析
- 词法分析
- 语义分析
- 符号汇总
汇编
汇编指令gcc -c test.c
汇编完成之后就停下来,结果保存在test.o
中。
vs下汇编生成的文件是.obj后缀,gcc汇编生成的文件是.o后缀
结论:汇编过程中将汇编代码转换为二进制(机器指令)
链接
回过头看编译阶段有一个步骤是符号汇总
,汇编过程中会形成符号表
,而链接过程会进行符号表汇总
符号表的合并和重定位
编程中,符号表是一种数据结构,用于存储程序的全局变量
、函数
和其他符号的信息。编译器使用它来跟踪程序中使用的所有符号及其属性。
符号表在编译过程中创建,并用于解析代码中的符号引用。它还用于检测错误,例如未定义的符号或同一符号的多个定义。
注意:test.c文件中声明的Add函数也要单独生成一个符号表,这个Add函数由于是因为声明检测出来的,所以地址为无效地址无效地址的符号表在重定位时会消失
符号表合并的意义:
在程序运行时调用Add函数时会去找到符号表中是否有Add函数,并且调用的是具有有效地址的Add函数,如果我们调用的是add函数,但是符号表中没有add函数,因此会链接型错误
符号表的合并重定位可以帮助我们在链接时检查是否存在调用的函数
合并段表
在链接期间程序需要将目标文件和链接库连接起来,最终才能生成可执行程序
目标文件是有格式的二进制文件,gcc产生的目标文件格式是elf ,这种格式将目标文件分成了很多段,可执行程序的文件也是elf,因此目标文件中的对应段会相互合并,存放在可执行文件中
一张图回顾程序运行环境
tips:计算机语言的发展和程序运行时经历的过程相反
运行环境
程序执行的过程:
-
程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中(单片机),程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
-
程序的执行便开始。接着便调用
main函数
。 -
开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
-
终止程序。正常终止main函数;也有可能是意外终止(
abort
函数)。