编译器 gcc/g++
背景知识
编译过程分为四个阶段:
| 阶段 | 说明 |
|---|---|
| 1. 预处理 | 进行宏替换、去注释、条件编译、头文件展开等 |
| 2. 编译 | 生成汇编代码 |
| 3. 汇编 | 生成机器可识别代码(目标文件) |
| 4. 链接 | 生成可执行文件或库文件 |
gcc 编译选项
基本格式
bash
gcc [选项] 要编译的文件 [选项] [目标文件]
预处理(进行宏替换)
- 功能:宏定义、文件包含、条件编译、去注释等
- 说明 :预处理指令是以
#号开头的代码行 - 选项 :
-E(让 gcc 在预处理结束后停止编译过程) - 输出文件 :
.i文件(已经过预处理的 C 原始程序)
示例:
bash
gcc -E hello.c -o hello.i
编译(生成汇编)
- 功能:检查代码规范性和语法错误,将代码翻译成汇编语言
- 选项 :
-S(只进行编译而不进行汇编,生成汇编代码) - 输出文件 :
.s文件(汇编代码)
示例:
bash
gcc -S hello.i -o hello.s
汇编(生成机器可识别代码)
- 功能 :将
.s汇编文件转成目标文件(二进制目标代码) - 选项 :
-c - 输出文件 :
.o文件(目标文件)
示例:
bash
gcc -c hello.s -o hello.o
链接(生成可执行文件或库文件)
- 功能:将目标文件链接成可执行程序
- 说明:成功编译后进入链接阶段
示例:
bash
gcc hello.o -o hello
动态链接和静态链接
在实际开发中,多个源文件之间存在依赖关系。每个 .c 文件独立编译成 .o 文件,为了满足依赖关系,需要将这些目标文件进行链接,形成可执行程序。
静态链接
缺点:
- 浪费空间 :多个程序对同一个目标文件有依赖时,每个可执行程序都含有一份副本(如多个程序都调用
printf(),则每个程序都包含printf.o) - 更新困难:库函数代码修改后,需要重新编译链接形成可执行程序
优点:
- 可执行程序中已具备所有执行所需的内容
- 运行时速度快
动态链接
动态链接把程序按模块拆分成相对独立的部分,在程序运行时才将它们链接在一起,而不是像静态链接那样把所有模块都链接成一个单独的可执行文件。
查看可执行程序依赖的动态库:
bash
$ ldd hello
linux-vdso.so.1 => (0x00007fffeb1ab000)
libc.so.6 => /lib64/libc.so.6 (0x00007ff776af5000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff776ec3000)
ldd命令用于打印程序或者库文件所依赖的共享库列表。
库的概念
C 程序中的 printf 函数并没有在代码中定义实现,stdio.h 中也只有声明。实际实现被放在 libc.so.6 库文件中。
在没有特别指定时,gcc 会到系统默认搜索路径 /usr/lib 下进行查找,链接到 libc.so.6 库函数,从而实现 printf 函数------这就是链接的作用。
静态库和动态库
| 类型 | 说明 | 后缀名 | 链接时机 |
|---|---|---|---|
| 静态库 | 编译链接时,把库文件的代码全部加入到可执行文件中,运行时不再需要库文件 | .a |
编译时 |
| 动态库 | 编译链接时不加入代码,程序执行时由运行时链接文件加载库 | .so |
运行时 |
注意事项:
gcc在编译时默认使用动态库- 可以使用
file命令验证生成的二进制程序是否为动态链接
示例:
bash
gcc hello.o -o hello # 默认动态链接
gcc 其他常用选项(了解即可)
| 选项 | 说明 |
|---|---|
-E |
只激活预处理,不生成文件,需要重定向到输出文件 |
-S |
编译到汇编语言,不进行汇编和链接 |
-c |
编译到目标代码(.o 文件) |
-o |
文件输出到指定文件 |
-static |
对生成的文件采用静态链接 |
-g |
生成调试信息,GNU 调试器(gdb)可利用该信息 |
-shared |
尽量使用动态库,生成文件较小,但需要系统提供动态库 |
-O0 |
优化级别 0:不进行优化 |
-O1 |
优化级别 1:缺省值(默认级别) |
-O2 |
优化级别 2 |
-O3 |
优化级别 3:优化级别最高 |
-w |
不生成任何警告信息 |
-Wall |
生成所有警告信息 |
说明 :
-O0、-O1、-O2、-O3是编译器的优化选项的 4 个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高。