编译过程可大致分为以下五个步骤
(1)词法分析:将源文件流拆分为以Token表示的字符表,给语法分析程序使用
(2)语法分析:根据各个词法单元的第一个分量构造语法树
(3)语义分析:根据符号表和语法树检测源程序
(4)中间代码生成和优化:根据语义分析输出,生成机器语言的中间表示,如三地址码
(5)代码生成和优化:把中间表示形式映射到目标机器语言
hello.c的源码如下:
#include <stdio.h>
int main(){
printf("hello,world!");
}
在编译时添加–save-temps,用于保存中间文件,添加–verbose用于查看详细工作流程
gcc hello.c -o hello --save-temps --verbose
可以看到GCC的编译主要包括四个阶段,即
预处理 Preprocess
编译 Compile
汇编 Assemble
链接 Link
该过程分别使用了cc1,as和collect2三个工具:
其中cc1是编译器,保证hello.c->hello.i->hello.s
之后由as汇编器将hello.s汇编为hello.o目标文件
最后由链接器将ld命令封装,将C语言运行库CRT中的目标文件以及动态链接库.so进行链接,使程序可执行
gcc -E hello.c -o hello.i
…………
预处理阶段的主要功能有:
包含头文件:预编译器负责处理#include预处理指令,将指定的头文件内容复制到源代码文件中。
宏替换:预编译器处理#define预处理指令,将定义的宏在源代码中进行替换。
条件编译:预编译器处理#if,#ifdef,#ifndef,#else,#elif和#endif等预处理指令,根据指定的条件决定是否编译某部分代码。
删除注释:预编译器会移除源代码中的所有注释,使其不会出现在生成的目标代码中。
引用其他预处理指令:预编译器还可以处理如#error,#pragma等其他预处理指令。
gcc -S hello.i -o --masm=intel -fno-asynchronous-unwind-tables
GCC默认使用AT&T格式的汇编语言,编译时添加选项–masm=intel可以将其指定为intel模式,-fno-asynchronous-unwind-tables用于生成没有cfi宏的汇编指令,以提高可读性
编译阶段将经过预处理的代码转换为机器语言,通常为汇编语言
gcc -c hello.s -o hello.o
file hello.o
objdump -sd hello.o -M intel
此时的目标文件hello.o是一个可重定位文件,可以使用objdump命令查看。
在其中可以发现,由于未进行链接,对象文件中符号的虚拟地址还未确定,字符串“hello”的地址被设定为了0x0000,作为参数传递字符串地址的rdi寄存器被设置为0x0,而call puts指令中puts()函数的地址被设置为下一条指令的地址0x18
gcc hello.o -o hello
objdump -sd hello -M intel
链接分为动态链接和静态链接,gcc默认动态链接,可加-static改为静态。
链接阶段主要包括地址和空间分配,符号绑定和重定位