0 前提
0.1 4个过程
预处理(preprocessing) ----------------- gcc -E
编译(compilation) ------------------ gcc -S
汇编(assembly) -------------------- as
连接(linking) --------------------- ld
0.2 gcc指令
- -o:指定生成的输出文件;
- -E:仅执行编译预处理;
- -S:将C代码转换为汇编代码
- -wall:显示警告信息;
- -c:仅执行编译操作,不进行链接操作。
1. 预编译
1.1 预编译主要做以下4件事:
- 展开所有头文件
- 宏替换
- 去掉注释
- 条件编译
- 即对#ifndef #endif进行判断检查
1.2 示例
源码:
//#include <stdio.h>
// #include "calculation.h"
#include "cpp_add.h"
#define TEST
#ifndef TEST
#define MAX_NUM 100
#else
#define MAX_NUM 10
#endif
int main()
{
int x = 1;
int y = 3;
int a = MAX_NUM;
square(a);
return 0;
}
预编译:
2. 编译
2.1 编译主要做以下2件事
编译将代码转为汇编代码,并且这个步骤做了2件很重要的工作:
- 编译器在每个文件中保存一个函数地址符表,该表中存储着当前文件内包含的各个函数的地址
- 这一步要生成汇编代码,即一条一条的指令,而调用函数的代码会被编译成一条call指令,指令后面跟的是jmp指令的汇编代码地址,而jmp指令后面跟的才是“被调用的函数编译成汇编代码后的第一条指令”的地址,但是给call指令后面补充上地址的工作是链接时候的事情。
2.2 示例
还是上面的demo执行:gcc -S main.c
查看生成的main.s文件,也是汇编文件:
.file "main.c"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $1, -12(%rbp)
movl $3, -8(%rbp)
movl $10, -4(%rbp)
movl -4(%rbp), %eax
movl %eax, %edi
call square@PLT
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.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. 汇编
汇编实际上指把汇编语言代码翻译成目标机器指令的过程。
目标文件由段组成。通常一个目标文件中至少有两个段:
- 代码段:该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。
- 数据段:主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的。
3.1 主要完成以下3件事
- 根据汇编指令和特定平台,把汇编指令翻译成二进制形式;
- 合并各个section,合并符号表;
- 生成.o文件
3.2 示例
4. 链接
4.1 主要完成以下3件事:
- 合并各个.obj文件的section,合并符号表,进行符号解析;
- 符号地址重定位;
- 生成可执行文件
4.2 示例