%% # 纲要 > 主干纲要、Hint/线索/路标 # Q&A #### 已明确 #### 待明确 > 当下仍存有的疑惑 **❓<font color="#c0504d"> 有什么问题?</font>** # Buffer ## 闪念 > sudden idea ## 候选资料 > Read it later %% # GCC 工具集 > **GCC** (全称为 **GNU Compiler Collection**) GCC 是一组 **==编译工具套件==**,也可称之为 "**编译系统**",包括**预处理器、编译器、汇编器、链接器**。 在 Linux 下安装 **gcc/g++ 工具集** 后**将包括上述多个命令行工具**,其中命令行工具 `gcc` 本身是 "**==编译器驱动程序==**"。 > [!info] GCC 编译系统 > > "**GCC 编译器**" 严格来说应称为 "**==GCC 编译系统/编译工具套件==**",其提供的**命令行工具 `gcc`** 即为 "**==GCC 驱动程序==**"(**GCC 编译系统**的**编译器驱动程序**) > > ![[_attachment/05-工具/GNU 工具/gcc 编译器.assets/IMG-gcc 编译器-32225953A2FB3CC56995FC203A23A224.png|639]] > ^vy25k6 > [!info] 命令行工具 `gcc` 即为 "**GCC 驱动程序**" > > **`gcc` 命令**会自动**调用/运行** GCC 工具套件中的各项工具来**完成整个编译链接过程**。 ^qondex <br><br><br> # gcc 命令 命令:`gcc [opt...] infile` 编译源代码,生成可执行文件。 - `-o outfile` :**指定输出文件的文件名**。包括可执行文件、目标文件、汇编文件、预处理后的源文件等。 - 未指定时,生成的可执行文件名**默认为 `a.out`**,而目标文件、汇编文件等则**默认为源文件名+相应后缀**。 - `-std=<>`: 指定编译所用的语言标准。例如`c++11`,`c++17` 等。 - `-v` :打印出编译过程中的**详细信息**。包括: - **编译器版本** - **配置参数**(编译器查找头文件和库文件的路径等)、 - 具体**编译阶段**(预处理、编译、汇编和链接)和它们使用的命令等。 - `-v` 选项提供的详细信息有助于诊断问题原因,特别是在排查编译器配置或环境设置问题时。 - `-g` : 编译时**生成调试信息**(即 **`debug` 模式**) - 编译器将在生成的**目标文件**或**可执行文件**中**包含额外的调试信息**(因而会带来更大的程序体积),可供 `gdb` 等调试。 - **搜索路径相关**: - `-Idir`:指定 **==头文件==** 搜索路径 - 多个路径时,需分别使用多个 `-I `指定,搜索顺序将按出现顺序进行。 - `-Ldir`: 指定 **==库文件搜索路径==**(**针对 `-l` 列出的所要链接的库**),如指定当前目录 ` -L.` - 多个路径时,需分别使用多个 `-L` 指定,搜索顺序将按出现顺序进行。 - **即使库文件就在当前目录,编译器默认也不会去找的**,所以`-L.`选项不能少。 - `-l<library>`:指定 **==所要链接的库==**。 - 若库的名称是`libmylib.a`,则为`-lmylib`,即**去掉`lib`前缀和文件后缀**。 - **链接相关** - `-static` 指示链接器**对程序使用的==所有库==(包括标准库和第三方库)采用==静态链接==**(gcc 默认对标准库采用动态链接) - `-static-libgcc` 、`-shared-libgcc` : **控制如何链接到 gcc 的运行时库(libgcc)**<br>libgcc 包含了一些基础的运行时支持,例如整数和浮点数的辅助函数、堆栈展开等。 - `-shared-libgcc` **生成一个动态链接 libgcc 的可执行文件**(**默认行为**) - 即程序在运行时会依赖系统中的 libgcc 共享库。 - `-static-libgcc` **生成一个静态链接 libgcc 的可执行文件** - GCC 将 libgcc **静态链接**到最终的可执行文件或库中。 - **编译控制相关**:**控制停止的阶段** - `-E` :只对源代码进行预处理,输出 **预处理后的源代码** 到 **`stdout`** - => 搭配 `-o file` 或 `> file` 可产出 **==预处理==后的源代码文件**(`.i` 文件) - `-S` :只对源代码进行预处理、编译,产出 **==汇编==代码文件** (`.s` 文件) - `-c` :只对源代码进行预处理、编译、汇编,但**不执行链接**,产出汇编所得的 **==可重定位==目标代码文件**(`.o` 文件) - `-save-temps`:产出所有**中间步骤文件**。 - `-shared` 指示编译器**生成一个共享库**(`.so`),而不是可执行文件。 - 创建共享库时,需要搭配使用 `-fpic`(Position Independent Code)选项,确保生成的代码适用于动态链接。 - `-fpic` 指示编译器生成 "**位置无关的代码**" ```shell $ gcc -save-temps main.c $ ls a.out main.c main.i main.o main.s ``` - `-Dmacro[=defn]`:定义一个宏。 `-D` 选项用于**在编译时指定一个宏定义**而无需修改源代码。 - `-D` 选项后面**直接跟宏的名称**,如果需要**为宏指定值**,则使用 `=` 连接,如 `-DMACRO=1`。 - 示例:`gcc -Dgtest_force_shared_crt=ON`。 - **警告信息相关**: - `-w` :**禁止**所有警告消息 - `-Wall`:开启**大多数常用编译器警告**,包括:未使用的变量、参数,未初始化的变量,错误的数据类型转换,可能的逻辑错误等。 - 该并不包括 GCC 所有的警告。GCC 还有其他选项(如 `-Wextra` 和 `-pedantic`)可用于启用更多警告。 - `-Wextra`:启用**额外的警告**(针对那些 `-Wall` 未启用的),例如未使用的函数形参。 - `-Werror`:将所有 **==警告==消息** 升级为**错误信息**,即原先的警告将会导致编译失败。 - **优化等级相关**: - `-Og`:启用一些**基本的优化选项**,使得生成的代码更高效,但不会进行深度的优化。 - 这些优化主要包括消除冗余代码、简化表达式等,而不会改变代码的结构,保证代码的可读性和可调试性。 - 保证编译器生成的调试信息尽可能详细,使得在调试器(如`gdb`)中可以看到准确的变量值和代码行号。 - **`-O0`**:**完全不进行优化**,生成的代码最接近源代码,调试最方便,但执行性能较低。 - **`-O1`**:进行一些**基本的优化**,提升代码执行效率,但可能会影响部分调试信息。 - **`-O2`**:进行**更多的优化**,进一步提升性能,但调试信息可能更加不一致。 - **`-O3`**:启用更激进的优化,追求最高性能,调试信息可能严重失真。 - **`-Os`**:优化代码以减小生成的代码尺寸,适用于内存受限的环境。 - **`-Ofast`**:启用所有可能的优化,甚至包括违反标准的优化,追求极致性能。 - **依赖关系相关**: - `-M`:**生成==依赖关系描述规则==**(可供 make 使用),**输出到 `stdout`**; - 示例:`gcc -M main.c` 的输出类似于 `main.o: main.c header1.h header2.h /usr/include/stdio.h` - `-MM`:与 `-M` 类似,但 **==不包括系统头文件==**; - `-MF file` :指定**生成的依赖关系文件(dependency file)的文件名**。搭配其他选项一起使用时**表示输出到指定文件**。 - 该选项可搭配 `-M`、`-MM`、`-MD`、`-MMD` 等,指定**生成的依赖关系文件文件名**。 - `-MD`:等价于**执行 `-M -MF file`生成==依赖关系描述文件==** 并且同时 **==生成目标文件==**。 - **依赖关系文件名默认同源文件名,后缀 `.d`** =>默认**与目标文件 `.o` 同目目录**,可通过`-MD -MF <file>` 指定路径及文件名 - `-MMD`:与 `-MD` 类似,但 **==不包括系统头文件==** - 该选项常用于项目的 `Makefile`,以避免系统头文件的修改导致不必要的重编译 - `-MP`:同时为每个依赖的头文件**生成一个伪目标(phony target)**,可用于**防止在删除某个头文件后**导致 make 因 "没有规则来生成目标" 错误。 - `-MT target`:指定生成的**依赖规则中的目标的名称**。目标默认是源文件的对象文件名(例如 `main.o`),可**通过该选项覆盖**。 <br><br><br> # Debug 模式与 Release 模式 在 Visual Studio 中编译 C/C++程序时,可选 **Debug 模式**和 **Release 模式**。 在 `gcc` 编译下,选项 **==`-g`==** 即为 "**`Debug` 模式**",不带 `-g` 选项时即为 **"`Release` 模式"**。 使用 `-g` 选项后,生成的可执行文件中将包含各种 **==调试信息==** 及 **==符号表==**, 从而在调试时可以追踪 "**机器指令**" 与 "**源代码**" 的对应关系, 同时可见**各个函数名、变量名**,否则**将只显示纯粹的==机器指令==和==运行时内存地址==**。 > [!NOTE] 关于 `gcc -g` 生成的调试信息 > > 生成的调试信息包含了**详细的源代码信息,如变量的名称、类型、源代码中的行号等**,这些信息**不影响程序的运行性能**,但**会增加程序的大小。** > 调试器(如 gdb)需要依赖于这些调试信息,从而**正确地将执行中的程序映射回源代码**,进而提供**单步执行**、**断点设置**、**变量检查**等调试操作。 > > [!NOTE] 为包含完整调试信息,在**编译阶段**和**链接阶段** 都应使用 `-g` 选项: > > - `gcc -g -c file1.c file2.c`, 在编译源文件生成目标文件时使用 `-g` 选项,以便每个目标文件都包含调试信息。 > - `gcc -g -o prog file1.o file2.o` ,在链接目标文件生成可执行文件时,使用 `-g` 选项,以确保最终的可执行文件包含完整的调试信息。 > [!quote] 参见: [^1] > > ![[_attachment/05-工具/GNU 工具/gcc 编译器.assets/IMG-gcc 编译器-BD3D76860102EEF33C9A76F48C36FF14.png|751]] > <br><br><br> # 关于编译器的搜索路径 ### 头文件搜索路径 > [!NOTE] 关于 `gcc` 的 `-I dir`、`-isystem` 等选项 > > `-I dir`、`-iquote`、`-isystem`、`-idirafer dir` 均用于指定 "**头文件搜索路径**",但 **"作用顺序" 和 "针对的语法" 有不同** > > ![[_attachment/05-工具/GNU 工具/gcc 编译器.assets/IMG-gcc 编译器-DEF2EC67AE413FBA938FFC7CD9EDDF2A.png|701]] > > 总结: > > | | 编译器搜索头文件的路径顺序 (从左到右依次) | > | ---------------- | -------------------------------------------------------- | > | `#include <文件名>` | **`-I`**、 **`-isystem`**、**系统头文件目录**、**`-idirafer`** | > | `#include "文件名"` | **当前源文件所在的目录**、**`-iquote`**、 **同 `#include<>` 的四个路径** | > > ^1fmyfx <br><br> ## 库文件搜索路径 > [!NOTE] 查看编译器的 "库文件搜索路径" > > 编译器默认会找的库文件目录可以用 `-print-search-dirs` 选项查看: > > ![[_attachment/05-工具/GNU 工具/gcc 编译器.assets/IMG-gcc 编译器-18FFAA4BC8624F9314E2FFC24B4C2EF8.png|747]] > > ![[_attachment/05-工具/GNU 工具/gcc 编译器.assets/IMG-gcc 编译器-377D369666CE640B4A015E094103F976.png|712]] > > **编译器会在上述默认搜索路径以及`-L`选项指定的路径中查找用`-l`选项指定的库**。 > > 编译器会**优先考虑共享库**,例如`-lstack`,编译器会首先查找共享库 `libstack.so`,如果存在就链接它,若没有就再查找静态库 `libstack.a`。 > 如果希望编译器**只链接静态库**,可以指定 `-static`选项。 > <br><br><br> # 关于宏定义 > [!NOTE] 关于 `gcc -D` 选项 > > - **定义宏**:通过 `-D` 选项,可以定义一个宏,就好像它是在源文件中使用 `#define` 指令定义的一样。这对于**控制代码的编译过程和条件编译**非常有用。 > - **条件编译**:这些宏可以用于条件编译。**根据是否定义了某个宏,可以使用 `#ifdef`、`#ifndef` 和 `#endif` 指令来包含或排除代码块**。 > - **配置编译**:这种方法常用于基于命令行参数的编译配置,无需更改源代码就能控制不同的编译行为。 ### 调试宏 gcc 提供 C++ 标准库实现中,提供了一系列 "**调试模式宏**",**可供编译时调试检查**。 只需要在编译时,显式定义这些 "**调试模式宏**",即可执行。在未定义的情况下,这些宏将被指定为 "**空宏**",**从而不影响编译时开销**。 ![[_attachment/05-工具/GNU 工具/gcc 编译器.assets/IMG-gcc 编译器-BC6DEE0FEA3197DF245C5E285E6A1831.png|484]] ![[_attachment/05-工具/GNU 工具/gcc 编译器.assets/IMG-gcc 编译器-3065689FEB1AD44637E31A967850BFC1.png|822]] <br><br><br> # 关于链接行为 ## 选项说明 > [!caution] 关于 gcc 的 `-static` 选项 > > `-static`选项的意义是 "**对==所有库(包括标准库和第三方库)==采用静态链接**",**所有使用到的库代码会嵌入到最终生成的可执行文件**中。 > > 该选项适用于:需要生成独立运行的可执行文件,**不依赖运行时环境中的动态库、或目标环境不具备所需动态库** 的情况。 > > ![[_attachment/05-工具/GNU 工具/gcc 编译器.assets/IMG-gcc 编译器-E6BEF97F2E8B786CE4F477F36EA87EAC.png|837]] > > 使用静态库不需要 `-static` 选项,**链接器会对指定的静态库会进行静态链接**,而**对于系统标准库等仍会保持动态链接**。 ^3o8w7u > [!NOTE] 关于 `-fpic` | `-fPIC`选项 ——生成位置无关代码 > > `-f`后面跟一些编译选项,`PIC`是其中一种,表示**生成==位置无关代码==**(Position Independent Code): > > `-fpic` 表示**代码在编译时==不会固定到某个特定的内存地址==**,**允许其在运行时被加载到内存的任何位置**,从而能够**实现在多个应用程序间共享**,这是即是**共享库/动态链接库**所要求的特性。 > > 当生成**共享库**的时候,必须创建位置无关的代码,**让共享库使用任意的地址而不是固定的地址**,因此 `-shared` 必须与 `-fpic` 搭配使用。 > > 如果不使用 `-fPIC` 选项,则**编译后的动态库代码是位置相关的**,因而**动态载入时是通过 "==代码拷贝==" 的方式**来满足不同的调用,而**不能达到真正的代码段共享的目的**。 ^69vo2q > [!info] 关于 `-shared-libgcc` 选项 > ![[_attachment/05-工具/GNU 工具/gcc 编译器.assets/IMG-gcc 编译器-CBDB516B1747A26C2B7238816F89496D.png|1149]] ^ca2r5j <br><br> ## gcc 链接顺序 ![[02-开发笔记/03-计算机基础/编译与链接/链接#链接时的库声明顺序 🚨|链接]] <br> ## `-lpthread` 与 `-pthread` 的区别 二者都与 POSIX 线程库(pthread)的使用有关,区别在于: - `-lpthread` 是一个 **==链接器选项==**,指示链接器**链接 POSIX 线程库(`libpthread`)**。 - 示例: `gcc myprogram.c -o myprogram -lpthread`,通常在编译命令的最后部分指定链接选项。 - `-pthread` 是 **gcc 的==编译器选项==**, - 其作用包括两方面: - (1)**指示编译器启用线程支持**,包括 **为 pthread 库添加适当的预处理器定义和编译器标志**、其他优化等。 - (2)**指示链接器链接到 POSIX 线程库( `libpthread` )** - 示例:`gcc -pthread myprogram.c -o myprogram` 在绝大多数情况下,当程序**使用了 POSIX 线程(pthreads)** 时,应该使用 **==`-pthread` 选项==** 而不是 `-lpthread`。 `-pthread` 选项确保了**在编译和链接过程中**都启用对 POSIX 多线程的支持,正确编译并动态链接到 pthread 库。 <br> # gcc 编译优化等级 - `-O0`:不进行编译优化(**默认选项**) - `-O1`:启用**基本优化**,**减少目标文件体积和程序执行时间**。如**常量折叠、死代码消除和简单的循环优化**。 - 在不显著增加编译时间的情况下,提高代码运行效率。 - `-O2`:**在`-O1` 基础上**,启用**更多的优化技术**,如**循环展开、函数内联、优化调度**等; - 会避免可能导致代码膨胀或编译时间显著增加的优化。 - `-O3`:**在`-O2`的基础**上,并增加**函数调用优化**、**循环优化和向量化**等,打开`-finline-functions`,`-funswitch-loops` 等标签,**最大化代码运行速度**。 - 可能**增加代码大小和编译时间** - `-Ofast`:**在`-O3` 基础上**,并进行一些**非标准规定的优化**,例如**非标准的浮点优化,如假设没有NaN和无穷值、允许重新排序浮点运算**等,**追求极致性能**。 - 优化**违反标准**,牺牲**标准兼容性** - `-Og`:优化 **调试体验**,在**不影响调试信息**的情况下(保留`-g`选项所生成的完整调试信息),**应用 `-O1` 级别的一些优化以提升代码运行效率**。 - `-Os`:优化**目标文件的大小**,启用**大部分`-O2`的优化**,同时**避免会增加代码大小的优化,如循环展开**。 - 适用于需减小可执行文件大小的场景中使用。 - `-Oz`:极限优化代码大小(不总是适用于所有GCC版本)。与`-Os`类似,但进一步强调减小代码大小。 > [!NOTE] 优化选择建议 > > ![[_attachment/05-工具/GNU 工具/gcc 编译器.assets/IMG-gcc 编译器-90C17CEB6822243D67FB7DED3F95A88A.png|490]] > > > ![[_attachment/05-工具/GNU 工具/gcc 编译器.assets/IMG-gcc 编译器-2A052BF7875068E48E32ADCD8E072678.png|822]] <br><br> # gcc 标签扩展(Label Extensions) gcc 允许对**定义在当前函数中的 "==标签==" 通过 `&&` 运算符取地址** ,返回 **`void*` 类型的指针**。**该指针可被用于 `goto` 语句**。 示例: **基于 gcc 的标签扩展实现 `swtich-case` 语句** ```cpp title:imple_switch.c #include <stdio.h> // 基于gcc的标签扩展功能, 实现一个switch-case语句 void swtich_eg_impl(long x, long n, long *dest) { // Table of code pointers // gcc的标签扩展: 允许通过&&对当前函数内定义的标签取地址, 返回一个void*类型指针 static void *jt[7] = { &&loc_A, &&loc_def, &&loc_B, &&loc_C, &&loc_D, &&loc_def, &&loc_D }; unsigned long index = n - 100; long val; if (index > 6) { goto loc_def; } goto *jt[index]; // 跳转到对应的标签 loc_A: // case 100 val = x * 13; goto done; loc_B: // case 102 val = x + 10; // Walk through to loc_C loc_C: // case 103 val = x + 11; goto done; loc_D: // case 104, 106 val = x * x; goto done; loc_def: // default case. val = 0; done: *dest = val; } int main() { long value; swtich_eg_impl(5, 106, &value); printf("%ld\n", value); } ``` <br><br><br> # 生成依赖关系文件 GCC的 **`-M*` 系列选项**常用以生成 “**依赖关系文件**”: ——**以 ==make 规则==的形式列出==一个目标文件所依赖的所有头文件==**,**可供 Makefile 中 `include` 引入使用**。 > [!example] GCC `-M` 与 `-MM` 以及 `-MF file`选项使用示例: > > ![[_attachment/05-工具/GNU 工具/gcc 编译器.assets/IMG-gcc 编译器-F0B2B06A0CCEEF44F15B50380E47DD43.png|596]] > > [!example] GCC `-M` 与 `-MM` 以及 `-MF file`选项使用示例: > > ![[_attachment/05-工具/GNU 工具/gcc 编译器.assets/IMG-gcc 编译器-58C1AD866DC144271B8B422A77DC8DEC.png|568]] > > [!note] **依赖关系文件**常用于**供 Makefile 中引入**,从而**实现自动维护依赖关系**,而无需手动维护 Makefile 中的依赖项: > > ```makefile > # 自动生成依赖关系文件 > %.o: %.c > gcc -MMD -MF $(@:.o=.d) -c lt; > > # 包含所有生成的依赖关系文件 > -include $(SRCS:.c=.d) > ``` > > <br><br><br> # gcc 与 g++ 编译器的区别 - `gcc` 设计为 C 程序的编译器,默认使用 C 语言的编译模式。 - `g++` 设计为 C++ 程序的编译器,默认采用 C++的编译模式,会**自动链接 C++标准库**。 从编译命令上来看, **`g++` 等价于 `gcc -xc++ -lstdc++ -shared-libgcc`**: - 第一项是**编译选项,表示按照 C++ 模式编译**; - 第二项 **`-lstdc++` 表示链接到 C++标准库**(**`g++`会自动链接 C++标准库,无需指定,但使用 ==`gcc` 编译 c++程序==时需要手动指定**) - 第三项 **`-shared-libgcc` 表示动态链接到 `libgcc`** 由此可知,g++只是针对**编译标准**和**链接**做了调整。在**预处理和汇编**步骤上,**g++与 gcc 是完全等价的**。 - `gcc` 可用于编译 C++程序,但需要**手动指明链接 C++标准库并启用 `-shared-libgcc` 选项**。 - `g++` 也可用于编译 C 程序,但由于其使用的是 C++的编译标准,因此**可能不支持 C 语言的一些语法**,同时其链接到 C++标准库也是没有意义的。 ![[05-工具/GNU 工具/gcc 编译器#^ca2r5j]] <br><br> # gcc 链接器与 ld 链接器的区别 gcc 背后调用的是 `ld`,同时**其会==自动指明链接程序所需运行时库(动静态库)==例如 `libc` 等**。 如果直接使用 `ld` 进行链接,则**所有的依赖库都必须手动指定**。 <br> # C 语言标准 `gnu11` 与 `c11` 等的区别 gcc 中指定 C 语言标准时,有如下选项: - **GNU 扩展版本**(gcc 默认的 C 语言标准选项) - 特点:在 C 标准基础上,支持: - **启用 GCC 内建函数**如 `__builtin_*`; - **启用额外的库扩展**(如 POSIX 标准库函数) - **启用 GNU 扩展语法**(如嵌套函数,范围内跳转(`goto` 到函数内的标签)等) - 示例:`-std=gnu89, gnu99, gnu11, gnu17` 等 - **严格的 C 语言标准**(不包含任何 gcc 或 GNU 扩展) - 示例:`-std=c89, ansi, c11, c17, c23` %% # 其他 示例:查看 gcc/g++ 编译器版本 ![image-20230911223430082](_attachment/05-工具/GNU%20工具/gcc%20编译器.assets/IMG-gcc%20编译器-3F82839D12AC903A53D1B845A143BE60.png) > [!example] 示例:使用 g++编译 cpp 文件 > > ```shell > g++ -o hello main.cpp # 生成可执行文件`hello`。 > g++ -o hello -std=c++11 main.cpp > ``` > %% <br><br><br> # 参考资料 # Footnotes [^1]: [GDB 快速上手 - GDB简明教程 | 宅学部落](https://www.zhaixue.cc/gdb/gdb-quickstart.html)