预处理(Preprocessing)
- 任务:
处理源代码中以#
开头的预处理指令,包括:- 头文件包含(#include):将头文件(如 stdio.h)的内容直接插入到源文件中。
- 宏替换(#define):将代码中的宏定义(如 #define PI 3.14)进行文本替换。
- 条件编译(#ifdef #else #endif 等):根据条件决定代码的保留或删除(例如,区分调试和发布版本的代码)。
- 输出:生成一个经过预处理的中间文本文件(仍为可读文本,但已展开所有预处理指令)。
编译(Compilation)
- 任务:
将预处理后的代码转换为汇编语言代码。编译器会进行以下操作:- 词法分析:将代码分解成一个个单词(Token),例如识别关键字、变量名、操作符等。
- 语法分析:检查代码是否符合 C/C++ 语法规则(如括号是否匹配、语句是否完整)。
- 语义分析:检查代码的语义正确性(如变量是否先定义后使用、类型是否匹配)。
- 中间代码生成与优化:生成中间表示代码,并进行优化(如删除无用代码、优化循环),最终转换为汇编语言。
- 输出:生成汇编代码文件(如 Helloworld.s)。
汇编(Assembly)
- 任务:
汇编器(Assembler)将汇编语言代码转换为机器可识别的二进制目标文件(Object File,如 Helloworld.obj 或 Helloworld.o)。每个汇编指令会被映射为对应的机器码。 - 输出:生成二进制目标文件,此时文件中可能仍包含对其他函数(如标准库函数 printf)的未解析引用。
链接(Linking)
- 任务:
链接器(Linker)将多个目标文件(包括自身代码生成的 .obj 和依赖的库文件)链接成一个可执行文件(如 Helloworld.exe)。分为两种方式:- 静态链接:将库函数的代码直接复制到可执行文件中,最终文件较大,但运行时无需依赖外部库。
- 动态链接:仅记录对库函数的引用信息,运行时由操作系统加载对应的动态链接库(如 .dll 在 Windows 或 .so 在 Linux)。
链接过程会解析目标文件中的外部符号引用(如解决 printf 的具体实现来自哪里)。
- 输出:生成可直接运行的可执行文件。
运行(Execution)
- 任务:
操作系统加载可执行文件到内存中,创建进程,分配资源(如内存、文件句柄),然后执行程序的指令。程序从 main 函数开始执行,直到遇到 return 或 exit 等退出操作。
总结
C代码编译成可执行程序经过4步:
1)预处理:宏定义展开、头文件展开、条件编译等,同时将代码中的注释删除,这里并不会检查语法
2)编译:检查语法,将预处理后文件编译生成汇编文件
3)汇编:将汇编文件生成目标文件(二进制文件)
4)链接:C语言写的程序是需要依赖各种库的,所以编译之后还需要把库链接到最终的可执行程序中去
分步编译
预处理:gcc -E hello.c -o hello.i
编 译:gcc -S hello.i -o hello.s
汇 编:gcc -c hello.s -o hello.o
链 接:gcc hello.o -o hello
选项 | 含义 |
---|---|
-E | 只进行预处理 |
-S(大写) | 只进行预处理和编译 |
-c(小写) | 只进行预处理、编译和汇编 |
-o file | 指定生成的输出文件名为 file |
文件后缀 | 含义 |
---|---|
.c | C 语言文件 |
.i | 预处理后的 C 语言文件 |
.s | 编译后的汇编文件 |
.o | 编译后的目标文件 |
一步编译
gcc hello.c -o demo(还是经过:预处理、编译、汇编、链接的过程):