最近一段时间做了关于一些软件的交叉编译工作,由于觉得并没有学到许多东西,所以抽时间看了关于静态链接方面的内容,读了一部分《程序员的自我修养——链接、装载与库》,记录一些读书笔记及自己的总结。
一、GCC做了什么?
我们在Linux中经常使用
gcc 源文件名.c -o 目标可执行文件名
对一个编写好的C文件进行编译生成可执行文件,那么这之间的细节呢?
这里源文件名为 hello.c ,生成可执行文件名为 hello 为例子
gcc一个C源文件时对过程进行了隐藏,实际为:预编译 cpp、编译 cc1、汇编 as、链接 ld,其中每一步后的英文名是所使用的工具的名称。
那么各自的步骤到底干了什么呢?
1、预编译:处理.c文件中以“#”开头的预编译指令。
这些预编译指令包括了宏定义、条件预编译指令等等,此步中也会进行删除掉所有的注释等操作。当无法判断一个宏定义是否正确或者头文件是否正确包含的话,可以直接打开此步生成的文件进行检查。
gcc -E hello.c -o hello.i
2、编译:进行词法分析、语法分析、语义分析等工作,此步所生成的文件是以汇编代码构成的。
这一步是由编译器cc1完成(gcc中),主要分为词法分析、语法分析、语义分析、中间语言生成、目标代码生成与优化几步。
a.词法分析:将所有的C语言源代码分割为一系列的记号,这些记号主要为关键字、标识符、常量及特殊符号,比如表达式 a+b 在这步中就会被拆分为 a 和 b 两个标识符及 + 这个特殊符号。
b.语法分析:产生语法树,关于这步需要有一些数理逻辑的知识,即生成以表达式为节点的树,对应上面 a+b 的情况是 + 为一个节点,而 a 和 b 分别为左右子树的节点。
c.语义分析:确定每个节点的类型,比如整型、字符型等。
可以理解为在前一步的树的基础上在每个节点上都标示好类型,对于一些隐式转换及强制类型转换都会在这步中进行处理。d.中间语言生成:进行两步操作,首先将语法树转化为中间代码,然后在中间代码中对已经能够确定值的表达式进行求值。
其中中间代码一般为三地址码,即x = y op z
的形式,其中op代表特殊符号,然后如果有些表达式能够确定其值,比如 t1=5+6 这种两个常量相加的语句就直接进行计算。e.目标代码生成与优化:如字面意思,进行目标代码的生成与优化。
关于目标代码的生成与具体的硬件平台有关,而优化部分有部分操作,比如合适的寻址方式、对于乘法运算使用位移进行代替,这些如果有接触汇编代码会比较了解。
编译器流程图:
此步中生成的文件如果懂得汇编代码的话仍然是可读的
gcc -S hello.c -o hello.s
3、汇编:将汇编代码转变成机器可以执行的指令。
此步中是根据汇编指令与机器指令的对照表进行一一翻译,基本上一个汇编语句对应一条机器指令。
此步生成的文件已经没法读了,打开后全部是乱码,因为已经全部机制指令了。
gcc -c hello.s -o hello.o
或
gcc -c hello.c -o hello.o
或
as hello.s -o hello.o