【Linux】 gcc/g++ 编译器(内容详解+面试题)
gcc/g++ 编译器完整知识体系
一、背景知识
- gcc 与 g++ 区别
gcc:通用编译器,默认编译 C 语言;处理.cpp文件不会自动链接 C++ 标准库,不会启用 C++ 语法。g++:面向 C++ 的编译器驱动,底层依然调用 gcc;遇到 C/C++ 代码自动适配语法,默认链接 C++ 标准库。通用结论:编译 C 语言可用 gcc,编译 C++ 推荐直接使用 g++。
- C/C++ 源代码到程序完整四大阶段:预处理 → 编译 → 汇编 → 链接
二、gcc 四大编译阶段(分步实操)
2-1 预处理(Preprocess)
-E指令
gcc -E test.c -o test.i核心工作
- 删除注释;
- 处理
#include,把头文件内容直接复制进源文件;- 宏展开
#define,文本替换;- 解析条件编译:
#ifdef / #endif / #else;- 添加行号信息,用于编译报错定位。
- 产物:
.i文件,仍然是文本代码。2-2 编译(Compile)
-S【狭义编译】指令
gcc -S test.i -o test.s核心工作
- 词法分析、语法分析、语义检查;
- 校验语法错误(少分号、类型不匹配在此阶段报错);
- 将 C/C++ 代码翻译成汇编代码。
- 产物:
.s汇编文本文件。2-3 汇编(Assemble)
-c指令
gcc -c test.s -o test.o核心工作
- 把汇编指令翻译成 CPU 能识别的二进制机器码;
- 产物:目标文件
.o(ELF 格式二进制文件)⚠️ 重点:
.o文件内函数地址是未填充的、待定,无法直接运行。2-4 链接(Link)
gcc test.o -o test核心工作
- 合并多个
.o目标文件;- 解析未定义符号,找到库中对应的函数;
- 重定位:填充函数、变量真实内存地址;
- 组装生成最终可执行程序。
✍面试区分考点:
- 预处理 / 编译 / 汇编 只处理单个文件;
- 链接阶段才会跨文件、链接系统库。
三、动态链接 和 静态链接
1. 静态链接
编译时,将库代码完整复制到可执行程序内部
开启方式:编译增加参数
-staticgcc main.c -o main_static -static✅优点
- 程序独立运行,运行环境不需要依赖任何库;
- 启动速度略快,不需要运行时加载库。
❌缺点
- 可执行文件体积巨大;
- 多个程序使用同一个库,磁盘重复存放代码;
- 库升级后,必须重新编译程序,无法直接更新。
2. 动态链接(Linux gcc 默认方式)
编译不复制库代码,仅记录库依赖关系; 等到程序运行时,操作系统动态加载共享库。
✅ 优点
- 程序体积小;
- 多个进程共享内存中同一份库,节省内存;
- 升级库只需替换
.so文件,程序无需重新编译。❌ 缺点
- 运行环境必须存在对应版本动态库,缺失则直接报错
面试高频坑:动态链接节省内存,不是节省磁盘。
四、静态库 和 动态库(共享库)
📖库:大量可复用代码打包而成,对外只提供头文件,隐藏源码。
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
原因:
- 编译时 gcc 能通过
-L找到库;- 运行时操作系统动态链接器不会读取
-L参数。解决方案三选一:
- 将 so 放入系统库目录
/usr/lib;- 临时环境变量:
export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH- 修改
/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 默认函数是强符号,变量是弱符号;
多个重名符号链接规则:多个弱符号选任意一个;强弱共存选用强符号;多个强符号直接报重定义错误。