前言
文章内容参考自:《深入理解计算机系统》
本人是个小白,如有错误的地方,请大家帮忙指正,谢谢大家。
编译链接过程图示
注:此图片出处为 《深入理解计算机系统》
根据图示,一步一步分析。
gcc分步编译链接命令
1.预编译:
gcc -E main.c -o main.i
2.编译:
gcc -S main.i -o main.s
3.汇编:
gcc -c main.s -o main.o
4.链接:
gcc main.o -o main
预编译阶段
在《深入理解计算机系统》中提到,预处理阶段会根据以字符#开头的命令,修改原始的C程序。 比如#include<stdio.h> 就会读取系统中 stdio.h头文件的内容,并将其内容插入到程序文本中去。形成以 .i结尾的文件。
具体
- 删除所有的“#define”,并且展开所有的宏定义;
- 处理所有的条件预编译指令,“#if”、“#ifdef”、“#endif”等;
- 处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置;
- 删除所有的注释;
- 添加行号和文件名标识,以便于编译器产生调试用的符号信息及编译时产生编译错误和警告时显示行号;
- 保留所有的#pragma 编译器指令,因为编译器需要使用它们。
图示
示例,若我要编译链接的文件为这个main.c
在我执行这个预处理命令后,并查看此文件内容,会得到什么?
可以看出,程序直接变成七百多行,并且和上面所说内容一样,头文件内容也被插入进来,并且我的#define和 注释也都不见了
编译阶段
词法分析、语法分析、语义分析,代码优化,汇总符号。
图示
这个过程会检查代码中的错误,这里我代码正常就没有报错
那这个文件具体内容是什么?
实际上,编译器会将这个main.i的文本文件翻译成 main.s文本文件,它是一个由汇编语言实现的程序。 它相对于预编译的文件,行数和占用空间大小明显缩小很多。
错误示例
:比如我少写一个分号
在预编译阶段,是可以正常进行的,而在编译阶段才会报错,
汇编阶段
这一步,汇编器会将 main.s文件翻译成机器语言指令 ,即二进制格式 。把这些指令打包成一种 可重定位目标程序 的格式,就是常说的目标文件。这里就是 main.o文件了。在Windows中就是我们常见的obj文件。
简单记忆就是:将汇编指令翻译成二进制格式,生成各个 section,生成符号表。
命令格式:
图示
这时候的文件,在我们看来是一堆乱码。
注意
但值得注意的是: 在不同系统下格式不同。
注意上图的左上角开始,有个 ELF标志, 这是linux系统可执行程序的标志。
区别:
Linux可执行程序: ELF
Windows可执行程序: PE
链接阶段
- 合并各个 section,调整 section 的起始位移和段大小,合并符号表,进行符号解析,给符号分配虚拟地址。
- 符号重定位。
举例
比如我的程序里调用了printf函数, 而printf函数来自于一个 printf.o的单独的目标文件中,这个过程就是链接器将这个文件和我的main.o文件合并到一起 。最终形成main这个可执行目标文件,简称可执行程序。
图示
这时候就会形成 main 可执行文件,如图示中的绿色文件。
运行示例
源代码:
运行结果: 按照给出的循环条件,打印了五次。
常用方法
有人就会问,这想执行个程序还这么麻烦,有没有方便点的办法?
有的,可以直接一步到位
命令示例:
gcc -o main mian.c
运行示例
end
本人是个小白,如果文章中有错误的地方,请大家指正,谢谢大家。