从源代码到可执行文件:编译的四个阶段全解析
从源代码到可执行文件:编译的四个阶段全解析

明白了,我们可以将第一篇博客改为更正式、技术性更强的风格,去掉口语化表达,突出概念和操作步骤,同时保留示例和实践指导。下面是修改后的版本:


从源代码到可执行文件:编译的四个阶段解析

一、引言

在软件开发过程中,源代码必须经过一系列处理才能成为可执行程序。以 C 语言为例,源代码文件经过预处理、编译、汇编和链接,最终生成可在特定操作系统和硬件平台上运行的二进制文件。本篇文章将系统介绍这一过程,并演示每个阶段的具体操作。


二、示例程序

本文采用最基础的示例程序:

1// main.c
2#include <stdio.h>
3
4int main() {
5    printf("Hello, world!\n");
6    return 0;
7}

该程序用于展示从源代码到可执行文件的整个流程。


三、编译的四个阶段

编译过程可以分为四个阶段:

  1. 预处理(Preprocessing)

  2. 编译(Compilation)

  3. 汇编(Assembly)

  4. 链接(Linking)

每个阶段的作用、输入输出以及对应工具如下表所示:

阶段输入输出工具主要功能
预处理.c.i预处理器展开头文件、宏替换、处理条件编译
编译.i.s编译器将 C 代码翻译为汇编代码
汇编.s.o汇编器将汇编代码转为目标文件
链接.o可执行文件链接器符号解析、重定位、合并目标文件

四、阶段一:预处理

预处理阶段的主要工作是:

  • 展开 #include 指令的头文件内容;

  • 替换宏定义(#define);

  • 处理条件编译指令(#ifdef / #ifndef);

  • 删除注释。

执行命令:

1gcc -E main.c -o main.i

生成的 main.i 文件为 C 语言文本,其中头文件已完全展开,宏已替换,可用于后续编译阶段。


五、阶段二:编译

编译阶段将预处理后的代码转换为汇编代码:

1gcc -S main.i -o main.s

生成的 main.s 文件示例如下(x86 架构):

 1    .file   "main.c"
 2    .text
 3    .globl  main
 4main:
 5    pushq   %rbp
 6    movq    %rsp, %rbp
 7    leaq    .LC0(%rip), %rdi
 8    call    printf
 9    movl    $0, %eax
10    popq    %rbp
11    ret
12.LC0:
13    .string "Hello, world!"

可见:

  • .text 段用于存放可执行指令;

  • .string 段存放字符串常量;

  • 汇编代码已经与处理器指令集相关。


六、阶段三:汇编

汇编阶段将汇编代码转换为二进制目标文件 .o

1gcc -c main.s -o main.o

通过 nm 命令查看符号表:

1nm main.o

示例输出:

0000000000000000 T main
                 U printf

说明:

  • T main 表示函数 main 定义在 text 段;

  • U printf 表示符号 printf 未定义,需在链接阶段解析。


七、阶段四:链接

链接阶段将目标文件与所需库文件合并,生成最终可执行文件:

1gcc main.o -o main

此时,链接器完成以下操作:

  • 符号解析,将未定义符号与库函数匹配;

  • 地址分配,将各段放置在内存中的具体位置;

  • 重定位,修正函数调用和数据访问的地址。

执行程序:

1./main

输出:

Hello, world!

表示程序成功生成并运行。


八、可执行文件结构

Linux 系统下,可执行文件采用 ELF(Executable and Linkable Format)格式。可以使用以下命令查看结构:

1readelf -S main

示例输出(部分):

Section Headers:
  [Nr] Name      Type
  [ 1] .text     PROGBITS
  [ 2] .rodata   PROGBITS
  [ 3] .data     PROGBITS
  [ 4] .bss      NOBITS
  [ 5] .symtab   SYMTAB
  [ 6] .strtab   STRTAB

各段说明:

段名作用
.text存放可执行指令
.rodata只读数据(如字符串常量)
.data已初始化的全局变量
.bss未初始化的全局变量
.symtab / .strtab符号表与字符串表,用于调试

九、编译流程图

 ┌────────────┐
 │  main.c    │
 └────┬───────┘
      │ gcc -E
      ▼
 ┌────────────┐
 │  main.i    │ ← 预处理输出
 └────┬───────┘
      │ gcc -S
      ▼
 ┌────────────┐
 │  main.s    │ ← 汇编代码
 └────┬───────┘
      │ gcc -c
      ▼
 ┌────────────┐
 │  main.o    │ ← 目标文件
 └────┬───────┘
      │ gcc
      ▼
 ┌────────────┐
 │  main      │ ← 可执行文件
 └────────────┘

十、总结

本文介绍了 C 语言编译的四个阶段:

  1. 预处理:宏替换与头文件展开

  2. 编译:C 代码转汇编代码

  3. 汇编:汇编代码生成目标文件

  4. 链接:目标文件与库合并生成可执行文件


最后修改于 2025-12-29 10:37