在linux平台上 main.c ---> a.out
在windows平台上 main.c --> xx.exe
gcc -E main.c -o main.i // 预处理
gcc -S main.i -o main.s // 编译
gcc -c main.s -o main.o // 汇编
gcc main.o -o hello //链接
1. 预处理
xxx.c -> xxx.i
主要处理源代码文件中的以“#”开头的预编译指令。将.c文件变成.i文件,处理规则见下:
1、删除所有的#define,展开所有的宏定义。
2、处理所有的条件预编译指令,如“#if”、“#endif”、“#ifdef”、“#elif”和“#else”。
3、处理“#include”预编译指令,将文件内容替换到它的位置,这个过程是递归进行的,文件中包含其他文件。
4、删除所有的注释,“//”和“/**/”。
5、保留所有的#pragma 编译器指令,编译器需要用到他们,如:#pragma once 是为了防止有文件被重复引用。
6、添加行号和文件标识,便于编译时编译器产生调试用的行号信息,和编译时产生编译错误或警告时能够显示行号。
2.编译
xxx.i -> xxx.s 转换为汇编语言
把预处理之后生成的xxx.i或xxx.ii文件,进行一系列词法分析、语法分析、语义分析及优化后,生成相应的汇编代码文件.s。
1、词法分析:利用类似于“有限状态机”的算法,将源代码程序输入到扫描机中,将其中的字符序列分割成一系列的记号。
2、语法分析:语法分析器对由扫描器产生的记号,进行语法分析,产生语法树。由语法分析器输出的语法树是一种以表达式为节点的树。
3、语义分析:语法分析器只是完成了对表达式语法层面的分析,语义分析器则对表达式是否有意义进行判断,其分析的语义是静态语义——在编译期能分期的语义,相对应的动态语义是在运行期才能确定的语义。
4、优化:源代码级别的一个优化过程。
5、目标代码生成:由代码生成器将中间代码转换成目标机器代码,生成一系列的代码序列——汇编语言表示。
6、目标代码优化:目标代码优化器对上述的目标机器代码进行优化:寻找合适的寻址方式、使用位移来替代乘法运算、删除多余的指令等。
3.汇编
xxx.s —> xxx.o 转换为机器语言
将汇编代码转变成机器可以执行的指令(机器码文件)。 汇编器的汇编过程相对于编译器来说更简单,没有复杂的语法,也没有语义,更不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译过来,汇编过程由汇编器as完成。经汇编之后,产生目标文件(与可执行文件格式几乎一样)xxx.o(Windows下)、xxx.obj(Linux下)。
4.链接
将不同的源文件产生的目标文件进行链接,从而形成一个可执行程序。
1. 静态链接
函数和数据被编译进一个二进制文件。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件。
- 空间浪费:因为每个可执行程序中对所需的目标文件都要有一个副本,所以如果多个程序对同一个目标文件都有依赖,会出现同一个目标文件在存放多个副本。
- 更新困难:每当库函数的代码修改了,这个时候就需要重新进行编译、链接来形成新的可执行程序
- 运行速度快:在可执行程序中已经具备了所有执行程序所需要的文件,在执行的时候运行速度更快。
2. 动态链接
动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。
- 共享库:就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多个相同副本,而是这多个程序在执行过程中共享一个副本。
- 更新方便:更新时只需要替换原来的目标文件,而无需将所有的程序再重新进行链接。当程序下一次运行时,新版本的目标文件会自动加载到内存并且链接起来。
- 性能损耗:因为连接在程序运行时进行,所以每次执行程序都需要进行链接,所以性能会有一定损失。