C程序编译执行过程

最近在看CSAPP(Computer System: A Programmer’s Perspective, 深入理解计算机系统),看到第三章 程序的机器级表示,第二节讲了一下C程序的编译执行过程,以前在《C Primer Plus》上也看到过类似的内容,感觉比较有意思,遂记录一下。

源文件只是以字节序列的形式存储在磁盘上,它是如何运行在机器上的呢?

源文件会经历以下历程:

  1. 预处理(Preprocessing):将#include等头文件进行置换,形成完整的程序文件,一般预处理后,文件会变大。得到的是文本文件。
    例如:

    1
    2
    3
    4
    5
    6
    7
    // preprocessing.c
    #define PI 3.14
    int main()
    {
    int a = PI;
    return 0;
    }

    使用gcc -E preprocessing.c -o preprocessing.i预编译后:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // preprocessing.i
    # 1 "preprocessing.c"
    # 1 "<built-in>"
    # 1 "<command-line>"
    # 31 "<command-line>"
    # 1 "/usr/include/stdc-predef.h" 1 3 4
    # 32 "<command-line>" 2
    # 1 "preprocessing.c"

    int main()
    {
    int a = 3.14;
    return 0;
    }

    可以看到int a = PI;被宏定义的PI值进行了替换。

  2. 编译(Compilation):使用编译器,将完整的C代码编译成汇编代码。得到的仍然是文本文件。
    以3.2.2的程序为例:

    1
    2
    3
    4
    5
    6
    7
    // mstore.c
    long mult2(long, long);

    void multstore(long x, long y, long *dest){
    long t = mult2(x, y);
    *dest = t;
    }

    使用gcc -S mstore.i -o mstore.s进行编译后:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    	.file	"mstore.c"
    .text
    .globl multstore
    .type multstore, @function
    multstore:
    .LFB0:
    .cfi_startproc
    endbr64
    pushq %rbx
    .cfi_def_cfa_offset 16
    .cfi_offset 3, -16
    movq %rdx, %rbx
    call mult2@PLT
    movq %rax, (%rbx)
    popq %rbx
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc
    .LFE0:
    .size multstore, .-multstore
    .ident "GCC: (Ubuntu 10.2.0-13ubuntu1) 10.2.0"
    .section .note.GNU-stack,"",@progbits
    .section .note.gnu.property,"a"
    .align 8
    .long 1f - 0f
    .long 4f - 1f
    .long 5
    0:
    .string "GNU"
    1:
    .align 8
    .long 0xc0000002
    .long 3f - 2f
    2:
    .long 0x3
    3:
    .align 8
    4:

    以“.”开头的都是指导汇编器和链接器工作的伪指令,可以忽略。

  3. 汇编(Assemble):将汇编代码转换成机器代码,得到的是二进制的目标文件。
    使用gcc -c mstore.s -o mstore.o进行汇编;因为二进制文件无法直接查看,因此可以使用objdump -d mstore.o反汇编进行查看:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    mstore.o:     文件格式 elf64-x86-64


    Disassembly of section .text:

    0000000000000000 <multstore>:
    0: f3 0f 1e fa endbr64
    4: 53 push %rbx
    5: 48 89 d3 mov %rdx,%rbx
    8: e8 00 00 00 00 callq d <multstore+0xd>
    d: 48 89 03 mov %rax,(%rbx)
    10: 5b pop %rbx
    11: c3 retq
  4. 链接(Linking):链接多个目标文件以及库文件等,最终得到可执行文件。

总结这些只是为了了解C代码是如何变成机器代码,并最终被CPU执行的。当然我可以一步到位,直接由C代码得到可执行程序:
gcc mstore.c -o mstore