一个程序的源代码到变成可执行程序,要经过 --预处理—编译—汇编–链接四个过程。 1. 预处理都做了啥? 指令: gcc -E hello.c -o hello.i 预处理主要处理源代码中以“#”开头的预编译指令,主要包括:
将所有的“#define”删除并展开所有的宏定义。 处理所有条件预编译指令,比如“#if”"#ifdef""#else"“endif”
处理#include预编译指令,将被包含的文件插入到该编译指令的位置。 删除所有的注释 “//” “/**/”
添加行号和文件名标识,比如#2 “hello.c” 2, 以便编译时编译器产生调试用的行号信息*,及用于编译时,*产生编译错误或警告时能够显示行号
保留所有的#pragma编译器指令,因为编译器需要使用它们
2. 编译都做了啥? 指令:gcc -S hello.i -o hello.s 把预处理完的文件进行一系列词法分析,语法分析,语义分析,及优化后产生相应的汇编代码(目标代码)文件。 2.1 词法分析—将源代码的字符序列扫描分割成一系列的记号。例如: code: arry[index] = (index + 4) * (2 + 6) 上面语句会被拆分为:
记号类型arry标识符[左方括号index标识符]右方括号…略 2.2 语法分析 将扫描器产生的记号进行语法分析,从而产生语法树(syntax tree),语法树是以表达式(expression)为节点的树。例如:arry[index] = (index + 4) * (2 + 6) 这是一个包含赋值表达式,加法表达式,乘法表达式,数组表达式,括号表达式组成的复杂表达式。 符号和数字是最小的表达式,所以他们是末端子节点。 语法分析过程中会确定运算符号的优先级。 如果发现表达式不合法,比如括号不配对,表达式中缺少操作符,变编译器将报错。 2.3 语义分析 分析整条语句是否有意义,包括: 静态语义:编译期间可以确定的语义。通常包括声明和类型的匹配,类型转换 动态语义:运行期间可以确定的语义。如,0 作为除数。 语义分析后,语法树的表达式都被标示了类型 2.4 代码优化 将语法树转换成中间代码(常见的有三地址码—(例如 x = y op z)和P-代码) 中间代码使得编译器被分为前端和后端。前端负责产生机器无关的中间代码。后端负责将中间代码转换成目标机器代码(跨平台编译发生在后端)
3. 汇编都做了啥? 指令: gcc -c hello.s -o hello.o 汇编是将汇编代码转变成机器可以执行的指令,汇编过程没有复杂的语法,只是根据汇编指令和机器指令的对照表一一翻译过来。输出的是目标文件(object file)。
4. 链接都做了啥? 链接过程主要包括 地址和空间分配,符号决议和重定位。 连接器的主要功能是重定位,每个需要修正的地方叫做一个重定位入口(relocation entry),重定位就是给程序中每个这样的地址引用的位置“打补丁”,使它们指向正确的地址。