编译与链接

被隐藏的过程

$gcc hello.c
$./a.out
Hello world!
从源代码到可执行程序之间发生了什么?

第一步|预编译

gcc -E hello.c -o hello.i                 #C
cpp hello.c > hello.i #C++
  • 删除#define,展开对应的宏定义
  • 处理条件预编译指令,#if,#ifdef,#else等
  • 递归讲#include包含文件插入对应位置
  • 删除注释,添加行号,文件明标识,方便调试
  • 保留下面编译用的#prama编译器指令

第二步 | 编译

gcc -S hello.i -o hello.s
cc1 hello.c #合并预编译,编译
gcc -S hello.c -o hello.s #合并预编译,编译

对预处理的文件词法,语法,语义分析,并优化

第三步 | 汇编

as hello.s -o hello.o  #as 是汇编器
gcc -c hello.s -o hello.o
gcc -c hello.c -o hello.o #产生目标文件

从汇编代码到机器指令

第四步 | 链接

简单来说:链接器是将很多的目标文件(.o)链接起来

编译 | 了解一下

array[index]=(index+4)*(2+6);

词法分析

扫描器扫描分割,比如array是标识符,[是左括号等等,C语言的宏展开交给独立的预处理器。

语法分析

语法分析器已表达式为节点,构建语法分析树。

语义分析

静态语义分析:声明,类型匹配,类型转换

源码优化&目标代码生成及优化

源码优化:比如2+6被优化成8,目标代码生成及优化:删除多余指令,是有合适的寻址方式等等。

链接 | 略懂略懂

历史久远 | 从纸带说起

上图就是纸带,左侧是行号,试想要是插入指令在某一行,对应的在其中的目标地址就要全部重新算,这个过程即是重定位,由于地址是数字,变化会很复杂,所以才去地址符号化策略,譬如’foo’,无论实际地址如何变化,在编译时会重新计算’foo’地址,将引用到’foo’的指令修正到正确地址;

静态链接 | 模块拼装

重定位 | 地址

比如a模块用b模块的var变量,var=0x1,编译a时,var的地址为0x00 00 00 00(因为不知道实际地址),之后链接器会讲正确地址覆盖0x00 00 00 00,每个修正的地方叫重定位入口。