《程序员的自我修养》读书笔记——第二章

70 阅读2分钟

一、构建过程

预编译

gcc -E source.c -o source.i

处理以 “#” 开始的预编译指令

  • 删除所有的 “#define”, 展开所有的宏定义
  • 处理所有条件预编译指令,“#if”、“#ifdef”。
  • 处理 “#include” 指令,将包含的文件插入到预编译指令的位置。
  • 删除注释
  • 保留所有的 #pragma 编译器指令
  • 添加行号和文件名标识

源代码

#include <stdio.h>

#pragma pack(4)

#define PI 3.1415926

int main()
{
    #ifdef WINOS
        printf("WIN OS\n");
    #else
        printf("MAC OS\n");
    #endif
    printf("PI = %f\n", PI);
    printf("Hello World!\n");
    return 0;
}

预编译后

······
······

# 2 "source.c" 2

#pragma pack(4)

int main()
{



        printf("MAC OS\n");

    printf("PI = %f\n", 3.1415926);
    printf("Hello World!\n");
    return 0;
}

编译

将预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生成汇编代码文件。 编译是以C文件为编译单元的

gcc -S source.i -o source.s

汇编

将汇编代码转为机器指令,产生目标文件。

gcc -c source.s -o source.o

链接

若干变量和函数组成一个模块,一个源文件称为一个编译单元,链接主要是处理各个编译单元之间的符号引用。 包括地址和空间分配符号决议重定位

二、编译过程

  • 词法分析
    • 将源代码分解为单词符号。
  • 语法分析
    • 上下文无关算法。
    • 生成语法树。
    • 检查语法错误(括号不匹配、缺少操作符等)。
  • 语义分析
    • 静态语义分析(编译器可以确定的语义)。
    • 检查语义错误(声明和类型的匹配)。
  • 优化
    • 源码级优化:
      • 语法树转换成中间代码(三地址码)。
      • 将一些编译期可以确定值的代码优化掉,例如表达式 2 + 6 会被优化为数值 8。
      • 产生的中间代码是机器无关的。
    • 代码生成器:
      • 将中间代码转换为目标机器代码。
    • 目标机器代码优化

三、链接(静态链接)的基本过程

重定位 编译单元 main.c 引用了另一个编译单元 func.c 中的 foo() 函数。在编译 main.c 的时候并不知道 foo() 的具体地址,指令中 foo() 的地址就会被搁置。链接的时候,链接器会去 func 模块查找 foo() 的真实地址,并将 main 当中引用 foo() 的指令中的地址替换为真实地址。