gcc 编译过程

gcc 编译器可以将代码生成可执行文件,主要流程如下:

Linux-GCC编译过程1.png

计算机只认识机器码,任何高级编程语言编写的程序运行都需要将代码转换为机器码,最后通过链接生成可执行文件


预处理

主要是处理源代码中以 "#" 开头的预处理指令,比如 #include#define 等,将其置换后直接插入程序文本中,得到另一个 C 程序,通常以 .i 作为扩展名

  1. 递归处理 #include 预处理指令,将对应文件的内容复制到该指令的位置
  2. 删除所有的 #define 指令,并在其被引用的位置递归地展开所有的宏
  3. 处理所有的条件预处理指令,如:#if#ifdef#elif#else#endif
  4. 删除所有的注释
  5. 添加行号和文件名标识

编译

将预处理文件进行一系列的词法分析、语法分析、语义分析以及优化,最终生成汇编代码,通常以 .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

  1. 可执行程序 .exe
  2. 动态链接库 .dll
  3. 静态链接库 .lib

Linux ELF

  1. 可执行程序 .out
  2. 动态链接库 .so
  3. 静态链接库 .a

gcc 编译指令

以 C 语言编写一个简单的 hello.c 程序为例:

#include <stdio.h>int main()
{
    printf("Hello World\n");
    return 0;
}

处理过程:

Linux-GCC编译过程2.png

  • 编译 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.ihello.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 位