gcc编译过程
gcc 编译过程
gcc 编译器可以将代码生成可执行文件,主要流程如下:
计算机只认识机器码,任何高级编程语言编写的程序运行都需要将代码转换为机器码,最后通过链接生成可执行文件
预处理
主要是处理源代码中以
"#"
开头的预处理指令,比如#include
、#define
等,将其置换后直接插入程序文本中,得到另一个 C 程序,通常以.i
作为扩展名
- 递归处理
#include
预处理指令,将对应文件的内容复制到该指令的位置 - 删除所有的
#define
指令,并在其被引用的位置递归地展开所有的宏 - 处理所有的条件预处理指令,如:
#if
、#ifdef
、#elif
、#else
、#endif
等 - 删除所有的注释
- 添加行号和文件名标识
编译
将预处理文件进行一系列的词法分析、语法分析、语义分析以及优化,最终生成汇编代码,通常以
.s
作为扩展名
- gcc 默认使用
AT&T
格式的汇编语言,添加编译选项-masm = intel
可以指定为intel
格式 - 编译选项
-fno-asynchronous-unwind-tables
用于生成没有 cfi 宏的汇编指令,提高可读性 - 若
printf()
只有单一参数,gcc 的优化策略会将其替换成puts()
以提高性能
汇编
汇编器根据汇编指令与机器指令对照表进行翻译,通常以
.o
作为扩展名
- 此时的
.o
文件是一个可重定位文件,可以使用objdump -sd .o文件名 -M intel
查看其内容 - 由于此时还未进行链接,文件符号中的虚拟地址无法确定
链接
将目标文件及其依赖库进行链接,生成可执行文件。包括:地址和空间分配、符号绑定、重定位等
- gcc 默认为动态链接(添加编译选项
- static
可指定使用静态编译) - 链接操作由链接器(
ld.so
)完成,然后就会得到一个可执行文件,其包含了大量的库文件 - 链接完成后,上一步无法确定的虚拟地址就被修正为实际的符号地址,可以被加载到内存中正常执行
可执行文件
广义的可执行文件:文件中的数据是可执行代码的文件,例如
.out
、.exe
、.sh
、.py
狭义的可执行文件:文件中的数据是机器码的文件,例如
.out
、.exe
、.dll
、.so
Windows PE
- 可执行程序
.exe
- 动态链接库
.dll
- 静态链接库
.lib
Linux ELF
- 可执行程序
.out
- 动态链接库
.so
- 静态链接库
.a
gcc 编译指令
以 C 语言编写一个简单的 hello.c
程序为例:
#include <stdio.h>
int main()
{
printf("Hello World\n");
return 0;
}
处理过程:
- 编译
hello.c
,一步到位,默认生成a.out
可执行文件
gcc hello.c
- 编译
hello.c
,一步到位,并指定生成文件名为hello
可执行文件
gcc hello.c -o hello
- 只执行预处理,生成
hello.i
源文件
gcc -E hello.c -o hello.i
- 只执行预处理和编译,生成
hello.s
汇编文件
gcc -S hello.c
- 也可以由
hello.i
文件生成hello.s
汇编文件,即:对预处理文件进行编译
gcc -S hello.i -o hello.s
- 只执行预处理、编译和汇编,生成
hello.o
目标文件
gcc -c hello.c
- 也可以由
hello.i
或hello.s
生成hello.o
目标文件
gcc -c hello.i -o hello.o
gcc -c hello.s -o hello.o
- 由
hello.o
目标文件链接成可执行文件hello
gcc hello.o -o hello
按照上述方法生成的可执行文件
hello
,在使用 gdb 调试时会显示:(No debugging symbols found in hello)
因为 gcc 希望可执行文件更小、更快,默认是不加调试信息的
如果想要在编译时产生调试信息,需加上参数 -g
gcc -g hello.c -o hello
gcc 编译 32 位
gcc 可以指定编译为 32 位程序和 64 位程序,在 64 位机器上默认编译为 64 位程序
在 64 位系统下,安装下列库使 gcc 支持编译 32 位程序:
sudo apt install gcc-multilib g++-multilib module-assistant
在上述 gcc 编译指令中,加上参数 -m32
即可指定编译为 32 位,加上 -m64
即可指定编译为 64 位
评论