在 ANSI C (C语言标准)的任何⼀种实现中,存在两个不同的环境:
-
翻译环境->源代码在翻译环境中被转换为机器可执行的二进制指令,由编译和链接两部分完成
-
运行环境->源代码经过翻译环境转换成机器可执行的二进制指令后,在运行环境中被执行
下面我们讨论的是翻译环境,即翻译环境中的编译和链接的过程
一、编译
编译分为预处理、编译、汇编3个过程
编译过程中,每一个文件都是分开单独各自处理的,从最开始的源文件(与头文件)到最后编译好后的目标二进制文件
源文件.c –> 预编译代码.i –> 汇编代码.s –> 二进制目标文件.o(Unix/Linux系统中目标文件格式.o,Windows系统中.obj)
1.预处理
源文件.c –> 预编译代码.i
(过程中处理的对象是源文件.c与头文件.h,处理结束后生成预编译代码.i)
-
处理源文件中以#开头的预编译指令:
1.将代码中用#define定义的文本代替内容还原为原信息,并将#define定义语句代码删除
2.处理所有的条件编译指令,如: #if、#ifdef、#elif、#else、#endif
3.将#include包含的头文件内容插入到文件中来
-
删除所有的注释
-
添加行号和文件名标识,以方便后续编译器生成调试信息
-
保留所有的#pragma的编译器指令,编译器后续会使⽤
2.编译
预编译代码.i –> 汇编代码.s
将预处理后的文件进行词法分析、语法分析、语义分析
2.1词法分析
将代码内容视为字符内容,以关键字、标识符、字面量、特殊字符4种记号将字符分割
例如:
将上述代码视为字符内容以4种记号分割好后便得到了16个记号:
2.2语法分析
将记号以表达式为关系块块联系在一起,构成有理解意义的语法树
2.3语义分析
编译器对表达式进行语法层面的分析与检查
例如此图中如果等号两边类型不相同语法层面有错编译器会报警告
3.汇编
汇编代码.s -> 目标二进制文件.o
将汇编代码翻译成二进制的指令
二、链接
将各个单独编译好的目标二进制文件与链接库文件链接合并生成一个可执行程序
首先,因为每个文件都是单独编译过来的,创建内存空间时谁也没见过谁,等到后面放在同一个程序里时难免会有内存地址共用冲突的问题,编译器采取的做法是:
每一个文件都会生成它里面有关函数、全局变量的名称对应它的地址的符号表,然后将所有的符号表进行合并与重定位,下图中文件1与文件2会将其符号表进行合并,发现Add有重复的要删掉重复的Add时,经过符号决议,选择删掉文件2的,Add函数的地址实现了重定位,得到有效的地址与空间分配,然后再将合并好的符号表交给链接好的可执行程序里,分配空间
这样,链接就实现了多文件合成可执行程序的同时保证了地址和空间的有效正确分配,解决了一个项目中多文件、多模块之间互相调用的问题
文件2中Add函数未找到其地址(即Add函数不在文件2中定义,在别的文件里定义的),就会先将其地址置为空