C/C++ 中的编译与链接
编译与链接主要为下述步骤,其中前三个步骤就是广义上的编译。
- 预处理,把一个
.c
源文件处理成.i
预处理文件。 - 编译,把
.i
预处理文件进一步处理成.s
汇编文件。(狭义上的编译) - 汇编,把
.s
汇编文件最终处理得到.o
机器码文件。 - 链接,把多个
.o
机器码文件链接成可执行文件。
准备源代码
为了演示这个过程,我编写了以下两个文件作为源代码。1
2
3// myheader.h
1
2
3
4
5
6
7
8
9
10
11
12// main.c
/**
* 我是注释
* 我也是注释
*/
int main() {
// 我还是注释
int i = M + N;
return 0;
}
接下来,我将以此文件为基础进行演示。
广义上的编译
预处理
首先是预处理,通过 gcc -E main.c -o main.i
命令将 main.c 预处理成 main.i
main.i 文件内容如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# 0 "main.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 0 "<command-line>" 2
# 1 "main.c"
# 1 "myheader.h" 1
# 2 "main.c" 2
int main() {
int i = 999 + 666;
return 0;
}
可以看的出来 #include
和#define
这些预处理指令,都已经成功的执行。
#include
成功的把myheader.h
引入到了main.c
中#define
的宏定义也完成了文本替换- 源代码中的注释也全部被去掉了
编译(狭义上的编译)
接着是狭义上的编译,通过 gcc -S main.i -o main.s
命令将 main.i 编译成 main.s
main.s 文件内容如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 .file "main.c"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $1665, -4(%rbp)
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 14.2.1 20240910"
.section .note.GNU-stack,"",@progbits
可以看的出来预处理好的文件已经被编译成了汇编代码。
懂一些汇编的话,应该能看出来 movl $1665, -4(%rbp)
这一行汇编代码就是给变量 i 赋值,rbp 寄存器偏移 -4 的位置就是 i 的地址。
汇编
接着是汇编,把汇编代码汇编成机器码,通过 gcc -c main.s -o main.o
命令将 main.s 汇编成 main.o
main.o 是机器码文件,已经是一个二进制文件了。之前讲过的打包静态库所使用到的就是 .o
文件。
详见 在 C++ 中使用.a 静态库 和Linux 中的 ar 命令 这两篇文章。
链接
最后我们可以使用 gcc main.o -o main
将编译获得的多个 .o
文件链接成为可执行程序。在本文的例子中,只有一个 .o
文件。
最终 main
就是可执行程序。
目录结构
1 | test/ |