【Linux】 gcc/g++ 编译器(内容详解+面试题)

📅 2026/7/29 6:05:53 👁️ 阅读次数 📝 编程学习
【Linux】 gcc/g++ 编译器(内容详解+面试题)


gcc/g++ 编译器完整知识体系

一、背景知识

  1. gcc 与 g++ 区别
    • gcc:通用编译器,默认编译 C 语言;处理.cpp文件不会自动链接 C++ 标准库,不会启用 C++ 语法。
    • g++:面向 C++ 的编译器驱动,底层依然调用 gcc;遇到 C/C++ 代码自动适配语法,默认链接 C++ 标准库。

    通用结论:编译 C 语言可用 gcc,编译 C++ 推荐直接使用 g++。

  2. C/C++ 源代码到程序完整四大阶段:预处理 → 编译 → 汇编 → 链接

二、gcc 四大编译阶段(分步实操)

2-1 预处理(Preprocess)-E

指令

gcc -E test.c -o test.i

核心工作

  1. 删除注释;
  2. 处理#include,把头文件内容直接复制进源文件;
  3. 宏展开#define,文本替换;
  4. 解析条件编译:#ifdef / #endif / #else
  5. 添加行号信息,用于编译报错定位
  6. 产物:.i文件,仍然是文本代码

2-2 编译(Compile)-S【狭义编译】

指令

gcc -S test.i -o test.s

核心工作

  1. 词法分析、语法分析、语义检查;
  2. 校验语法错误(少分号、类型不匹配在此阶段报错);
  3. 将 C/C++ 代码翻译成汇编代码。
  4. 产物:.s汇编文本文件

2-3 汇编(Assemble)-c

指令

gcc -c test.s -o test.o

核心工作

  1. 把汇编指令翻译成 CPU 能识别的二进制机器码;
  2. 产物:目标文件.o(ELF 格式二进制文件)

⚠️ 重点:.o文件内函数地址是未填充的、待定,无法直接运行


2-4 链接(Link)

gcc test.o -o test

核心工作

  1. 合并多个.o目标文件;
  2. 解析未定义符号,找到库中对应的函数;
  3. 重定位:填充函数、变量真实内存地址;
  4. 组装生成最终可执行程序。

✍面试区分考点:

  1. 预处理 / 编译 / 汇编 只处理单个文件;
  2. 链接阶段才会跨文件、链接系统库。

三、动态链接 和 静态链接

1. 静态链接

编译时,将库代码完整复制到可执行程序内部

开启方式:编译增加参数-static

gcc main.c -o main_static -static

优点

  1. 程序独立运行,运行环境不需要依赖任何库;
  2. 启动速度略快,不需要运行时加载库。

缺点

  1. 可执行文件体积巨大;
  2. 多个程序使用同一个库,磁盘重复存放代码;
  3. 库升级后,必须重新编译程序,无法直接更新。

2. 动态链接(Linux gcc 默认方式)

编译不复制库代码,仅记录库依赖关系; 等到程序运行时,操作系统动态加载共享库。

✅ 优点

  1. 程序体积小;
  2. 多个进程共享内存中同一份库,节省内存;
  3. 升级库只需替换.so文件,程序无需重新编译。

❌ 缺点

  1. 运行环境必须存在对应版本动态库,缺失则直接报错

面试高频坑:动态链接节省内存,不是节省磁盘。


四、静态库 和 动态库(共享库)

📖库:大量可复用代码打包而成,对外只提供头文件,隐藏源码。

4.1 静态库libxxx.a

本质:多个.o目标文件用ar打包归档。

制作流程
# 1. 生成目标文件 gcc -c func.c -o func.o # 2. ar打包静态库 ar rcs libfunc.a func.o

参数解释:

  • r:插入 / 替换模块
  • c:创建库
  • s:生成索引(加快链接速度)
使用静态库编译
gcc main.c -o main main.c -L./ -lfunc
  • -L:指定库搜索目录
  • -lfunc:链接libfunc.a(自动补齐 lib 前缀和.a 后缀)

4.2 动态库(共享库)libxxx.so

编译参数说明

-fPIC:生成位置无关代码(动态库必备)-shared:生成共享库

gcc -fPIC -shared func.c -o libfunc.so
使用动态库编译
gcc main.c -o main -L./ -lfunc

⚠重点问题:编译成功,运行报错找不到 so

原因:

  1. 编译时 gcc 能通过-L找到库;
  2. 运行时操作系统动态链接器不会读取-L参数。

解决方案三选一:

  1. 将 so 放入系统库目录/usr/lib
  2. 临时环境变量:export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
  3. 修改/etc/ld.so.conf配置库路径,执行ldconfig更新缓存

静态库 vs 动态库对照表

对比项静态库 libxxx.a动态库 libxxx.so
链接时机编译期程序运行期
程序大小
运行依赖不需要原库运行环境必须存在对应 so
库升级重新编译程序直接替换库文件
内存占用多进程重复加载内存中只存在一份

五、gcc/g++ 其他高频编译选项(拓展了解)

# 指定输出文件名 -o # 调试信息,gdb调试必备 -g # 开启优化 O0~O3,O0无优化,O3最高优化 -O1 -O2 -O3 # 警告提示 -Wall -Wextra # 指定头文件搜索路径 -I # 指定库搜索路径 -L # 链接指定库 -l # 生成位置无关代码(编译动态库必备) -fPIC # 强制静态链接 -static

六、配套面试真题

基础问答题

gcc 编译四个阶段分别是什么,每个阶段做什么?

预处理(宏展开、处理头文件)→编译(语法检查生成汇编)→汇编(生成.o 目标文件)→链接(合并目标文件、填充地址生成可执行程序)


gcc 和 g++ 有什么区别?能不能用 gcc 编译 cpp 代码?

g++ 底层调用 gcc;g++ 自动启用 C++ 语法、链接 C++ 标准库;

gcc 编译 cpp 不会默认链接 std 库,容易出现未定义引用错误,不推荐。


编译动态库为什么需要 -fPIC?不加会怎么样?

PIC = 位置无关代码。动态库会被多个进程加载到不同内存地址;

不加 - fPIC 生成绝对地址代码,无法共享加载,编译报错。


进阶面试题

为什么动态库编译正常,运行时提示找不到共享库?如何解决?

-L仅作用于编译阶段;运行依靠动态链接器搜索路径。

解决方案:LD_LIBRARY_PATH、系统库目录、ldconfig 配置。


.o 目标文件、可执行文件、静态库、动态库都是什么格式?

Linux 下全部为 ELF 格式,只是类型标记不同。


头文件和库有什么区别?

头文件 (.h):声明,编译阶段使用;

库 (.a/.so):函数实现二进制代码,链接 / 运行阶段使用。


-g调试信息会影响程序性能吗?

不会。

调试信息独立存放,不会参与程序运行;

发布版本只需要 strip 剥离调试信息即可。


-O3 优化会有什么风险?

编译器激进优化,会调整代码执行顺序;

如果代码存在内存越界、未定义行为,优化后 bug 表现可能和无优化版本不一致。


代码 / 现象面试题

使用静态库和动态库同时存在时,gcc 默认优先链接哪一个?

默认优先动态库;加-static参数强制选择静态库。


多个程序使用同一个动态库,内存中存在几份库代码?

一份(只读代码段共享;数据段每个进程独立)。


拔高思考题(大厂常问)

什么是符号重定位?发生在哪个阶段?

链接阶段。.o文件中调用外部函数只有符号名,没有真实地址;

链接器找到符号真实地址,回填到代码,该过程为重定位。


弱符号、强符号是什么?链接时有什么规则?

gcc 默认函数是强符号,变量是弱符号;

多个重名符号链接规则:多个弱符号选任意一个;强弱共存选用强符号;多个强符号直接报重定义错误。