1、结论
分为下面四个步骤。
-
预处理:会处理所有以#开头的指令。
-
文件包含:将
#include指令指定的头文件插入到源代码中。 -
宏展开:将
#define定义的宏替换为宏值。 -
条件编译:根据
#if和#ifdef等条件,决定是否包含某部分代码。
-
-
编译:编译器将预处理后的代码转换为汇编代码,格式为.s
-
汇编:汇编阶段是将汇编代码(.s)转换为机器代码(.o,一堆机器能看懂的0和1二进制文件)的过程。
-
链接:将不同的目标文件和库文件连接成一个可执行文件的过程。
第一次看不懂正常,我第一次也不懂。通读下面的示例就明白了。
2、示例
假设我有两个代码:main.c和add.c
#define NUM 10
int main(){ add(5, NUM); return 0;}int add(int a, int b){ return a+b;}以这俩为例讲解比较好,已经去掉非核心的那些内容,能够用最简单的情况、最少的代码,展现这个过程。
2.1、预处理
main.c里面包含#define,进行符号替换,得到下面的代码
int main() { add(5, 10); // NUM 被展开为 10 return 0;}add.c里面没有,保持原样。
2.2、编译
通常是采用gcc工具来进行编译。经常linux下编程的同学应该熟悉这个shell指令。做单片机的同学可能会陌生,把他理解是一段编译的命令就可以,不必细究。
gcc -S main.c # 生成 main.s 汇编文件gcc -S add.c # 生成 add.s 汇编文件分别得到下面的代码,非常纯正的汇编。
.file "main.c" .text .globl main .type main, @functionmain: push %rbp mov $5, %eax # 5 被传递给 a mov $10, %ebx # NUM 被替换为 10,传递给 b call add # 调用 add 函数 pop %rbp ret .file "add.c" .text .globl add .type add, @functionadd: push %rbp mov %edi, %eax # 将 a 存储在 eax add %esi, %eax # 将 b 加到 eax 中 pop %rbp ret2.3、汇编
执行下面
gcc -c main.s -o main.o # 编译 main.s 到 main.ogcc -c add.s -o add.o # 编译 add.s 到 add.o把上一步得到的.s汇编代码,编译成.o文件。里面的内容是一堆01机器码。下面的代码不必细究,我随意编造的,会意就可以。
00000000 00000000 00000001 00000010 00000000 00000000 00000000 00000000 # mov eax, 500000000 00000000 00000001 00000010 00000000 00000000 00000000 00001010 # mov ebx, 1000000000 00000000 00000001 00000010 00000000 00000000 00000000 10000000 # call add (address)00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 # ret...00000000 00000000 00000001 00000010 00000000 00000000 00000000 01000000 # mov eax, [esp+4]00000000 00000000 00000001 00000010 00000000 00000000 00000000 01100000 # add eax, [esp+8]00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 # ret...2.4、链接
下面是最后一步。把这些机器码链接起来。
这里为什么要把main.o和add.o链接起来呢?要是还有个abcd.o文件要不要链接?
因为main.o里面用到了add.o的函数,所以链接起来,abcd.o没有用到,就不连接。
gcc main.o add.o -o main # 链接生成可执行文件 main下面是连接后的可执行文件,一个没有abcd.o干预的二人世界。
实际上链接器会做符号解析与重定位,并非简单地把文件拼接,比如这里的第三行就有地址的替换。为了简单我们就把他理解为追加就可以啦,又不是专业做这个的😋
00000000 00000000 00000001 00000010 00000000 00000000 00000000 00000000 # mov eax, 500000000 00000000 00000001 00000010 00000000 00000000 00000000 00001010 # mov ebx, 1000000000 00000000 00000001 00000010 00000000 00000000 00000000 00100000 # call 0x2000 (地址替换)00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 # ret
00000000 00000000 00000001 00000010 00000000 00000000 00000000 01000000 # mov eax, [esp+4]00000000 00000000 00000001 00000010 00000000 00000000 00000000 01100000 # add eax, [esp+8]00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 # ret....o文件是二进制格式,包含代码段、数据段、符号表等结构,不是简单的纯机器指令序列。工作中用到再细致了解就可以,初学者不用纠结。
想要运行可执行文件,只需要执行下面的shell代码
./main部分信息可能已经过时