《程序员的自我修养》p2 编译和链接过程

在这里插入图片描述
在这里插入图片描述
GCC编译器:https://blog.csdn.net/liubing8609/article/details/82695528
a.out是GCC可执行文件,这样是一次性完成了编译链接的全部过程

被隐藏的过程

Gcc的编译流程分为了四个步骤:

  • 预处理,生成预编译文件(.文件):gcc –E hello.c –o hello.i
  • 编译,生成汇编代码(.s文件):gcc –S hello.i –o hello.s
  • 汇编,生成目标文件(.o文件):gcc –c hello.s –o hello.o
  • 链接,生成可执行文件:gcc hello.o –o hello

在这里插入图片描述

预处理

将.c文件和相关头文件预处理为.i文件

简单来说,预处理就是将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些代码输出到一个 “.i” 文件中等待进一步处理。
头文件的包含,宏定义的拓展,条件编译的选择

预编译过程主要处理那些源代码文件中以 "#"开始的预编译指令。比如"#include"、"#define"等,
主要处理规则如下:
• 将所有的 “#define” 删除,并且展开所有的宏定义
• 处理所有条件预编译指令,比如"#if"、"#ifdef"、"#elif"、"#else"、"#endif"
• 处理"#include"预编译指令,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件
• 删除所有的注释"//“和”/* */"
• 添加行号和文件名标识,比如 #2 “hello.c” 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号
• 保留所有的 #pragma 编译器指令,因为编译器需要使用它们
经过预编译后的 .i 文件不包含任何宏定义,因为所有的宏已经被展开,并且包含的文件也已经被插入到 .i 文件中。所以当我们无法判断宏定义是否正确或头文件包含是否正确的时候,可以查看预编译后的文件来确定问题。

编译

把预处理完的文件进行一系列词法分析、语法分析、语义分析以及优化后生成相应的汇编代码文件
在这里插入图片描述
《编译原理》
在这里插入图片描述

汇编

把汇编代码转换成机器可以执行的代码
因为通常最终的可执行目标文件由多个不同模块构成,所以在生成单个模块的机器语言目标文件的时候,不可能确定每条指令或每个数据的最终地址。因此把汇编生成的机器语言目标代码称为可重定位的目标文件
首先要记住的的是,在汇编过程中是对每一个文件单独进行。生成单个文件的可重定位的目标文件。

重定位

执行 gcc –c main.s –o main.o,
add()函数只被声明没有被定义,不知道add()的内容,更不知道add()的地址;

但是在汇编代码中我们必须知道调用函数的地址。而在生成的可重定位目标文件中,总不能把调用函数的地址空着吧?需要填一个虚假地址,在链接以后,将多个可重定位目标文件合并在一起,才有可能知道 add() 的真正位置。

// main.c
#include <stdio.h>
void add(int, int);
int main() {
    add(10, 20);
    return 0;
}
// func.c
void add(int x, int y) {
    return x+y;
}

链接

链接就是生成a.out文件的过程,即可执行目标文件,

也就是CPU可以直接放入内存,可以执行了
源码文件(.c)文件经过编译器编译成目标文件(Object file,一般扩展名为 .o 或 .obj),目标文件和库(Library)一起链接形成最终的可执行文件
链接过程主要包括了地址和空间分配(Address and Storage Allocation)、符号决议(Symbol Resolution)和重定位(Relocation)等这些步骤。
链接的过程就是一个模块拼装的过程,将各种模块通过符号拼装成一个整体,即 a.out 。链接的主要内容就是把各个模块之间相互引用的部分都处理好,使得各个模块之间能正确的衔接。

以 helloworld程序为例,C语言静态库库在Linux中位于/usr/lib/libc.a中,Windows中位于IDE所附带的运行库中比如VC++,里面包含了目标文件,形成libc.a这个静态库文件
printf函数位于printf.o文件中
同时printf.o也包含了对外部符号的引用
在这里插入图片描述

编译器做了什么

在这里插入图片描述

词法分析

源代码程序被输入到扫描器(Scanner),扫描器对源代码进行简单的词法分析,运用类似于有限状态机(Finite State Machine)的算法可以很轻松的将源代码字符序列分割成一系列的记号,不同的记号放到对应的表
词法分析产生的记号一般可以分为如下几类:关键字、标识符、字面量(包含数字、字符串等)和特殊符号(如加号、等号)。在识别记号的同时,扫描器也完成了其他工作,比如将标识符存放到符号表,将数字、字符串常量存放到文字表等,以备后面的步骤使用。
对于下面的源代码:
在这里插入图片描述
在这里插入图片描述

语法分析

语法分析器(Grammar Parser)将对由扫描器产生的记号进行语法分析,从而产生语法树(Syntax Tree)。整个分析过程采用了上下文无关语法(Context-free Grammar)的分析手段。由语法分析器生成的语法树就是以表达式(Expression)为节点的树。
在这里插入图片描述

语义分析

语义分析对这个语句是否真正有意义进行分析由语义分析器来完成,仅涉及静态语义的分析:声明和类型的匹配、类型的转换
经过该阶段后,整个语法树的表达式都被标识了类型
中间语言生成
对源码进行优化,将语法树转换成中间代码,中间代码跟目标机器和运行时环境是无关的。中间代码比较常见的是三地址码和P-代码

三代码结构: x = y op z
array[index] = (index + 4) * (2 + 6)
引入临时变量 t1 t2 t3
t1 = 2 + 6
t2 = index + 4
t3 = t2 * t1
array[index] = t3

优化:2+6 = 8
t2 = index + 4 t2 = t2 * 8 array[index] = t2

目标代码生成与优化

代码生成器将中间代码转换成目标机器代码,和具体的机器相关,包含字长、寄存器等
目标代码优化器对目标代码进行优化,比如选择合适的寻址方式、使用位移来代替乘法运算
编译之后存在的问题:其他模块的全局变量和函数地址无法确定

静态链接

当一个程序被分割成多个模块后,这些模块的拼接过程就是链接
链接的主要过程:
1) 空间与地址分配:扫描所有的输入目标文件,获得它们的各个段的长度、属性和位置,并且将输入目标文件中的符号表中所有的符号定义和符号引用收集起来,统一放到一个全局符号表。这一步中,链接器将能够获得所有输入目标文件的段长度,并且将它们合并,计算出输出文件中各个段合并后的长度与位置,并建立映射关系。
2) 符号解析与重定位:使用上面第一步中收集到的所有信息,读取输入文件中段的数据、重定位信息,并且进行符号解析与重定位、调整代码中的地址等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值