编译与连接主要分为4个步骤,分别是预处理、编译、汇编和连接
预处理(预处理文件)
g++ -E helloword.cpp -o helloworld.i
-E的编译选项,只执行到预编译,直接输出预编译结果
主要处理那些源代码文件只能够的以“#”开始的预编译指令,比如#include、#define等,主要处理规则如下
删除所有的#define,并展开所有的宏定义
处理所有的条件预编译指令,比如#if、#ifdef、#elif、#else、#endif
处理#include预编译指令,将被包含的文件插入到该预编译指令的位置
过滤所有的注释”//“和”/**/“
添加行号和文件名表示,便于编译时编译器产生调试用的行号信息等
保留所有的#pragma编译器指令,因为编译器需要使用它们
编译(最核心)(汇编文件)
把预处理完的文件进行一系列的词法分析、语法分析、语义分析以及优化后产生相应的汇编代码文件
g++ -S helloworld.i -o helloworld.s
array[index] = (index + 5)*(2 + 7);
词法分析
将源代码的字符序列分割成一系列的记号。将标识符存放到符号表中,将数字、字符串常量存放到文字表中。
语法分析
如果出现表达式不合法,比如括号不匹配、表达式缺少操作等,编译器报告语法分析阶段的相关错误。
对扫描器产生的记号进行语法分析,产生语法树
语义分析
语义分析由语义分析器完成。语法分析仅仅完成对表达式的语法层面的分析,但是他不能了解这个语句是否真的有意义。比如C语言中两个指针做乘法运算是没有意义的,但是在语法上是合法的
编译器能够分析的语义是***静态语义***(动态在运行期),通常包括声明和类型的匹配及类型的转换等。
经过语义分析阶段,整个语法树的表达式都被表示了类型,语义分析器还对符号表里的符号类型也做了更新
中间语言的生成
(2+7)可以被优化成9,因为她的值在编译i去可以确定
目标代码的生成与优化
经过上面五个步骤,index和array的地址还没有确定
事实上,定义其他模块的全局变量和函数在最终运行时的绝对地址都要在最终连接的时候才能确定,所以要将一个源代码文件编译成一个未连接的目标文件,然后由链接器最终将这些目标文件连接起来形成可执行文件
链接
每个源代码模块独立的编译,然后按照要求将他们组装起来,这个过程就是连接
库就是一组目标文件的包,就是一些常用的代码编译成目标文件后打包存放