>> >> >> Reference << << << <<<<<<Ref>>>>>>
>> >> >> Indexer << << << <<<<<<Idx>>>>>>
Matched: 0

Tags

    Categories

      Types

        Top Results

          C/C++ GCC
          M: 2026-04-21 - ljf12825

          基本概念

          基础语法

          gcc/g++ [选项] [源文件] [选项] [目标文件]
          

          常用选项

          基本选项

          选项作用
          -o <file>指定输出文件名
          -c只编译不链接,生成目标文件(.o)
          -E只进行预处理不编译
          -S编译到汇编语言,不汇编
          -v显示编译过程的详细信息
          输出默认文件名
          gcc main.c
          
          • 没有-o参数
          • GCC默认会把可执行文件叫a.out(这是Unix系统的传统名字)
          输出指定文件名
          gcc main.c -o main
          

          gcc内部进行以下四个阶段

          1. 预处理:处理宏定义、头文件包含等
          2. 编译:将预处理后的文件编译成汇编代码
          3. 汇编:将汇编代码转换成机器码
          4. 链接:将目标文件链接成可执行文件

          在链接完成后,中间文件会被删除掉

          预处理

          不指定文件名

          gcc -E main.c
          

          这会执行标准输出,将结果打印到屏幕上

          指定文件名

          gcc -E main.c -o main.i
          

          main.i这个名字可以自定义,扩展名也可以自定义,比如.txt, .abc

          常见约定

          扩展名含义常用性
          .i预处理后的C文件最常用
          .ii预处理后的C++文件最常用
          .txt文本文件可用
          无扩展名任意文件名不推荐

          同理对于.s, .o文件也是一样的,这对于gcc来说是可以的,但对于make或其他通过扩展名判断文件类型的工具来说未必行得通

          • 命令:GCC内部调用cpp
          • 作用:
            • 处理#include -> 把头文件内容插入进来
            • 处理宏定义#define
            • 处理条件编译#if/#ifdef
          • 输出一个扩展后的C文件
          编译
          gcc -S main.i -o main.s
          
          • 预处理后的文件 -> GCC转换称汇编代码
          • 这一步就是C -> 汇编

          main.s是gcc编译阶段最后的人类可读的文件

          汇编
          gcc -c main.s -o main.o
          
          • 汇编代码 -> 汇编器(as) -> 目标文件.o
          • .o文件是机器码二进制,基本不可读
          • 每个.c文件都会生成一个.o文件

          .o文件里有

          • 函数的机器指令
          • 数据段、符号表
          • 调用其他库函数的信息
          链接
          gcc main.o -o main
          
          • 链接器ld会把.o文件,系统库(libc, libm等),其他.o问阿金,合并成最终的可执行文件main,这时printf等函数才真正找到实现

          警告选项

          选项作用
          -Wall显示所有常见警告
          -Wextra显示额外警告
          -Werror将警告视为错误
          -w禁止所有警告
          -pedantic遵循标准C,提醒非标准用法

          GCC只会报真正严重的语法错误或链接错误

          • 语法错误(如缺括号、括号不匹配)
          • 类型错误(如int *p = "abc";
          • 链接错误(如调用不存在的函数)

          但像初始化、隐式转换、可能丢失精度等警告,默认不报

          GCC设计之初遵循“兼容老代码”原则,C语言历史上很多老程序有未初始化、隐式转换等现象,默认警告太多导致就代码编译报满屏,用户体验很差

          因此,开发中推荐的做法是

          gcc -Wall -Wextra -Werror main.c
          

          这样可以保证代码更安全、更规范,尤其是底层/库开发、游戏引擎开发、嵌入式开发等

          -Wall

          开启常用警告,比如

          • 未使用的函数或变量
          • 不匹配的格式化字符串(printf/scanf)
          • 可能的类型转换问题

          但不会开启一些“更严格的检查”

          -Wextra

          常见警告示例

          类型示例代码警告
          未使用参数int f(int x){return 0;}unused parameter 'x'
          空函数体void f(){}empty body (depends)
          不完全初始化数组int arr[5] = {1,2};missing initializer for element 2
          多余的逗号int arr[] = {1,2,};extra comma
          隐式int转换char c = 200;overflow in implicit constant conversion
          return没有值int f(){}control reaches end of non-void function

          不同版本GCC会有差异

          优化选项

          选项级别特点适用场景
          -O0不优化(默认)编译最快,调试信息完整开发调试阶段
          -O1基本优化基本优化,不增加编译时间快速测试,平衡模式
          -O2推荐优化常用优化,不增加代码体积生产环境默认选择
          -O3激进优化高级优化,可能增加代码体积性能更关键代码
          -Os体积优化优化代码体积嵌入式、移动设备
          -0fast极致优化突破标准限制的优化数值计算场景
          -O0
          gcc -O0 program.c -o program
          

          特点:

          • 编译速度最快
          • 代码直接对应源代码,不优化
          • 调试体验最好(变量不会被优化掉)
          • 适合开发和调试阶段
          -O1
          gcc -O1 program -o program
          

          开启的优化:

          • 删除未使用的变量和函数
          • 简单的常量传播
          • 基本的指令调度
          • 不增加编译时间

          示例:

          int test() {
              int a = 10;
              int b = 20;
              int c = a + b; // O1会直接计算为30
              return c;
          }
          
          -O2
          gcc -O2 program.c -o program
          

          在-O1的基础上增加:

          • 函数内联(小函数)
          • 循环展开
          • 更激进的指令调度
          • 寄存器分配优化
          • 死代码消除

          这是生产环境最常用的选项

          -O3
          gcc -O3 program.c -o program
          

          在-O2基础上增加:

          • 所有函数内联(即使是大的)
          • 循环完全展开
          • 预测分支优化
          • 向量化(SIMD指令)

          潜在问题:

          • 可能增加代码体积
          • 可能降低指令缓存命中率
          • 极少情况下反而更慢
          -Os
          gcc -Os program.c -c program
          

          特点:

          • 以-O2为基础
          • 额外优化代码体积
          • 不进行会增大代码体积的优化
          • 适合嵌入式系统、移动应用
          -Ofast
          gcc -Ofast program.c -o program
          

          特点:

          • 包含-O3所有优化
          • 加上-ffast-math(浮点运算优化)
          • 可能不符合IEEE/ANSI标准
          • 适合数值计算、科学计算
          具体优化选项
          控制内联
          # 禁止内联
          -fno-inline
          
          # 限制内联函数大小
          --param inline-unit-growth=20
          
          # 内联所有函数(即使没标记inline)
          -finline-functions
          
          循环优化
          # 循环展开
          -funroll-loops
          
          # 循环合并
          -fmerge-constant
          
          # 循环向量化
          -ftree-vectorize
          
          浮点优化
          # 快速数学运算(不保证精度)
          --ffast-math
          
          # 不进行浮点优化
          -fno-fast-math
          
          # 允许重排浮点运算
          -funsafe-math-optimizations
          
          内存优化
          # 消除未使用的变量
          -fdead-code-elimination
          
          # 合并全局常量
          -fmerge-all-constants
          
          # 地址空间布局随机化
          -fpie
          
          性能对比示例

          测试程序

          // benchmark.c
          #include <stdio.h>
          #include <time.h>
          
          #define ITERATIONS 10000000000 // 10^10
          
          int main() {
              clock_t start = clock();
              long long sum = 0;
          
              for (long long i = 0; i < ITERATIONS; i++) {
                  sum += i * i; // 浮点运算
              }
          
              clock_t end = clock();
              double time = (double)(end - start) / CLOCKS_PER_SEC;
              printf("Time: %.3f seconds\n", time);
              printf("Sum: %lld\n", sum);
              return 0;
          }
          

          编译指令

          gcc -O0 main.c -o main_O0 && time ./main_O0
          gcc -O1 main.c -o main_O1 && time ./main_O1
          gcc -O2 main.c -o main_O2 && time ./main_O2
          gcc -O3 main.c -o main_O3 && time ./main_O3
          gcc -Ofast main.c -o main_Ofast && time ./main_Ofast
          

          运行结果

          Time: 19.453 seconds
          Sum: 7032546979563742720
          
          real    0m19.459s
          user    0m19.452s
          sys     0m0.001s
          Time: 7.349 seconds
          Sum: 7032546979563742720
          
          real    0m7.334s
          user    0m7.350s
          sys     0m0.000s
          Time: 3.671 seconds
          Sum: 7032546979563742720
          
          real    0m3.646s
          user    0m3.670s
          sys     0m0.001s
          Time: 3.659 seconds
          Sum: 7032546979563742720
          
          real    0m3.639s
          user    0m3.660s
          sys     0m0.000s
          Time: 3.665 seconds
          Sum: 7032546979563742720
          
          real    0m3.650s
          user    0m3.665s
          sys     0m0.001s
          

          分析

          -O0 ~ -O1 有2.7x的提升
          -O1 ~ -O2 有2x的提升
          -O2 ~ -O3 的提升不明显
          -Ofast 轻微提升
          
          sum值在所有优化选项下相同,说明
          
          1. 所有优化都保持了计算结果一致
          2. 溢出是确定的(无符号/有符号溢出是正确行为)
          3. 编译器优化没有改变计算结果
          
          理论计算公式(取模2^64)
          sum = (N-1) * N * (2N-1) / 6
          N = 10 ^ 10
          结果应该是一个64位整数
          
          详细优化可以对比编译后的汇编文件进行分析
          

          调试选项

          调试选项本质上是在编译阶段保留足够的信息,从而让调试器能够还原源码级的执行过程

          -g
          gcc -g main.c -o main
          

          作用:

          • 生成调试信息(debug symbols)
          • 包括:
            • 变量名
            • 函数名
            • 源码行号
            • 类型信息

          没有-g,gdb只能看到汇编,看不到源码和变量名

          -g的不同等级
          -g0 // 不生成调试信息
          -g1 // 最少(只有函数/行号)
          -g2 // 默认
          -g3 // 最详细(包含宏)
          

          一般用-g,如果调试宏用-g3

          -ggdb

          含义:

          • 生成GDB专用增强调试信息
          • -g更GDB友好
          增强调试能力的选项
          • -fno-inline
            • 禁止函数内联,否则在GDB中打不到断点、无调用栈
          • -fno-omit-frame-pointer
            • 推荐在linux下使用,可保留rbp(x86_64),让GDB,perf,火焰图更加准确
          • -fvar-tracking
            • 提高变量追踪能力
          • -fvar-tracking-assignments
            • 更强的变量追踪
          调试信息格式 DWARF
          -gdwarf-4
          -gdwarf-5
          

          DWARF是调试信息标准

          • v4版本:稳定
          • v5版本:更现代(压缩更好)
          Sanitizer
          AddressSanitizer
          -fsanitize=address -g
          

          检查越界,user-after-free, stack overflow

          UndefinedBehaviorSanitizer
          -fsanitize=undefined -g
          

          检测UB

          优化与调试的关系

          -O会破坏调试体验

          -O0 // 无优化,最适合调试
          -O2 // 常规优化
          -O3 // 激进优化
          

          优化会导致:

          • 变量被优化掉(optimized out)
          • 代码重排
          • 内联函数
          • 行号错乱
          -Og

          -Og可以理解成“为调试而设计的优化级别”,它介于-O0-O1之间,但目标不是性能,而是在保证可调式性的前提下做一些“安全优化”

          -O0最好调试,但代码很原始,执行慢;-O2性能好,但变量消失,行号错乱,断点跳来跳去
          gcc引入-Og的目的就是在不破坏调试体验的前提下,让程序稍微接近真实执行状态

          -Og

          • 简单常量传播
          • 死代码消除(不会影响调试语义的)
          • 基本块简化
          • 一些轻量SSA优化

          不会做:

          • 激进内联
          • 复杂循环优化
          • 重排导致行号错乱
          • 变量寄存器化过度

          语言标准选项

          -std

          gcc -std=c99 file.c
          
          • c99
          • c11
          • gnu99
          • c++11
          • c++20

          使用示例

          基本编译

          # 编译单个文件
          gcc main.c -o main
          g++ main.cpp -o main
          
          # 编译并运行
          gcc main.c -o main && ./main
          

          多文件编译

          # 方式1:直接编译多个文件
          gcc file1.c file2.c file3.c -o program
          
          # 方式2:分步编译(适合大型项目)
          gcc -c file1.c -o file1.o
          gcc -c file2.c -o file2.o
          gcc file1.o file2.o -o program
          
          # 方式3:Makefile
          

          链接库文件

          链接静态库(.a)
          gcc -o program mani.c -L库路径 -l库名
          
          • -L:指定库文件所在的目录
          • -l:指定库名(去掉前缀lib和后缀.a
          • 多个库可以多次使用-l-lmath_utils -lm -lpthread
          链接动态库(.so)

          编译时链接

          # 基本语法(同静态库)
          gcc -o program main.c -L库路径 -l库名
          

          运行时需要指定库路径,动态库需要在运行时能找到,有三种方法

          设置LD_LIBRARY_PATH 环境变量
          # 临时设置
          LD_LIBRARY_PATH=./lib ./program
          
          # 或者导出环境变量
          export LD_LIBRARY_PATH=./lib:$LD_LIBRARY_PATH
          ./program
          
          编译时指定rpath
          # 使用$ORIGIN 表示可执行文件所在目录
          gcc -o program main.c -L./lib -lmath_utils -Wl, -rpath, '$ORIGIN/lib'
          
          # 或者使用绝对路径
          gcc -o program main.c -L./lib -lmath_utils -Wl,-rpath=/path/tp/lib
          
          # 多个rpath
          gcc -o program main.c -L./lib -lmath_utils -Wl,-rpath,'$ORIGIN/lib:/usr/local/lib'
          
          将库安装到系统路径
          # 复制到系统库目录(需要root权限)
          sudo cp lib/libmath_utils.so /usr/local/lib/
          sudo ldconfig # 更新库缓存
          
          # 然后直接运行
          ./program
          
          优先级和搜索顺序
          库搜索顺序
          1. -L指定的目录
          2. 环境变量LIBRARY_PATH(编译时)
          3. 系统默认目录(/lib, /usr/lib, /usr/local/lib)
          动态库运行时搜索顺序
          1. LD_LIBRARY_PATH环境变量
          2. rpath编译时指定的路径
          3. runpath编译时指定的路径
          4. 系统默认目录
          5. /etc/ld.so.conf配置的目录
          示例

          项目结构

          link-compile
          |-- include/
          |      |__ math_utils.h # 库头文件
          |-- src/
          |    |-- math_utils.c # 库源码
          |    |__ add.c # 额外功能
          |-- main.c # 测试程序
          |-- Makefile # 编译脚本
          |__ lib/ # 生成的库文件(编译后)
          

          指令

          # 编译库源文件为目标文件
          gcc -c -Iinclude src/math_utils.c -o src/math/utils.o
          gcc -c -Iinclude src/add.c -o src/add.o
          
          # 创建静态库
          ar rcs lib/libmath_utils.a src/math_utils.o src/add.o
          
          # 编译主程序并链接静态库
          gcc -o program main.c -Iinclude -Llib -lmath_utils
          
          # 或
          # 创建动态库
          gcc -shared -o lib/libmath_utils.so src/math_utils.o src/add.o
          
          # 编译主程序并链接动态库
          gcc -o program main.c -Iinclude -Llib -lmath_utils
          
          # 运行(需要指定库路径)
          LD_LIBRARY_PATH=./lib ./program
          

          包含头文件

          # 添加头文件搜索路径
          g++ program.cpp -o program -I/path/to/include
          

          编译过程

          gcc/g++编译分为四个阶段

          gcc vs g++

          Release & Debug

          GCC 本身没有Debug/Release模式,这是构建系统层面的概念

          GCC只有一堆编译选项,而Debug/Release只是这些选项的组合约定

          Debug/Release通常来自:CMake, Makefile, IDE,它们自己约定

          • Debug = 一组“好调试”的编译参数
          • Release = 一组“高性能”的编译参数

          GCC只关心三件事

          1. 是否生成调试信息
          2. 优化级别
          3. 宏控制行为

          所谓Debug/Release,就是这三者的组合

          • Debug = 有调试信息 + 低优化
          • Release = 无调试信息 + 高优化 + 关闭断言

          示例

          Debug

          -g -O0
          

          或者更现代的

          -g -Og
          

          特点

          • 有完整调试信息
          • 几乎不优化
          • 变量可见
          • 断点稳定

          Release

          -O2 -DNDEBUG
          

          特点:

          • 高优化(性能好)
          • 去掉调试信息
          • 去掉断言
          DNDEBUG
          #include <assert.h>
          
          assert(x > 0);
          

          Debug:

          -g
          

          assert生效

          Release

          -DNDEBUG
          

          assert被优化掉

          // 等价于
          ((void)0
          

          CMake行为

          在Cmake里

          cmake -DCMAKE_BUILD_TYPE=Debug
          

          等价于

          -g
          
          cmake =DCMAKE_BUILD_TYPE=Release
          

          等价于

          -O3 -DNDEBUG
          
          cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo
          

          等价于

          -O2 -g
          

          静态库

          动态库