本文面向 C/C++ 初学者,系统梳理 gcc/g++ 编译器的核心用法。全文围绕编译四大阶段(预处理、狭义编译、汇编、链接)展开,并深入对比静态库与动态库的区别,帮助读者理解从源文件到可执行文件的完整链路。此外,还整理了高频查看命令、头文件防重复包含写法、常用 gcc 选项速查表以及易混易错点,最后给出最简实操串。读者可按需定位到对应章节,快速掌握 gcc/g++ 的实用技能。
一、基础区分
- gcc :面向 C 语言 的编译器
- g++ :面向 C++ 语言 的编译器
补充:gcc 也可以编译 C++,但不会自动链接 C++ 标准库;日常 C++ 开发优先直接用 g++。
二、C/C++ 程序编译四大阶段
下面用一张流程图直观展示从源文件到可执行文件的完整编译链路:
flowchart TD
A[源文件 test.c / test.cpp] -->|gcc -E| B[预处理产物 test.i]
B -->|gcc -S| C[汇编代码 test.s]
C -->|gcc -c| D[目标文件 test.o]
D -->|gcc test.o -o test| E[可执行文件 test]
A -. 阶段1:预处理 -E .-> A1[头文件展开 / 宏替换 / 删除注释]
B -. 阶段2:狭义编译 -S .-> B1[词法 / 语法 / 语义分析,生成汇编]
C -. 阶段3:汇编 -c .-> C1[汇编指令翻译成二进制机器码]
D -. 阶段4:链接 .-> D1[合并 .o / 解析符号 / 重定位 / 链接系统库]
源文件
.c/.cpp分阶段执行,gcc 参数控制停在对应阶段
-
预处理(Preprocess)
-Ecppgcc -E test.c -o test.i- 工作:头文件
#include展开、#define宏替换、删除注释、#ifdef条件编译筛选分支、处理#pragma - 产物:
.i文件,纯文本,没有语法检查
- 工作:头文件
-
狭义编译(Compile)
-Scppgcc -S test.i -o test.s- 工作:词法分析、语法分析、语义检查,生成抽象语法树 AST,翻译成汇编指令
- 产物:
.s汇编代码文件,报错大多集中在这个阶段
-
汇编(Assemble)
-ccppgcc -c test.s -o test.o- 工作:汇编指令翻译成二进制机器码
- 产物:可重定位目标文件
.o - 特点:包含机器码、符号表、重定位信息;符号地址未补齐,不能直接运行
-
链接(Link)
cppgcc test.o -o test- 工作:合并多个
.o目标文件并解析符号引用、重定位、链接系统库 - 产物:ELF 格式可执行文件 / 库文件
- 工作:合并多个
一键编译:
gcc test.c -o test,内部自动走完上面 4 步,不保留中间.i/.s/.o
三、静态库与动态库
链接阶段的核心任务之一是把代码中用到的库函数合并进最终产物,而库分为静态库和动态库两种,二者在链接时机、产物形态和使用方式上差异明显。
3.1 静态库(Static Library)
- 概念 :在链接阶段 把库中需要的目标文件代码直接复制合并进可执行文件,生成后不再依赖库文件。
- 常见后缀 :Linux 下为
.a(如libm.a),Windows 下为.lib。 - 生成方式 :先用
gcc -c编译出目标文件,再用ar归档工具打包。
cpp
# 编译目标文件
gcc -c add.c -o add.o
打包成静态库
ar rcs libadd.a add.o
链接静态库
gcc main.c -L. -ladd -o main
- 优点 :程序运行时不依赖外部库文件,部署简单;加载速度快。
- 缺点 :可执行文件体积较大 ;库更新后需要重新编译链接整个程序。
3.2 动态库(Dynamic Library / Shared Library)
- 概念 :链接阶段只记录库的引用信息 ,不复制代码;程序运行时才由系统动态加载库文件到内存。
- 常见后缀 :Linux 下为
.so(如libm.so),Windows 下为.dll。 - 生成方式 :编译时加
-fPIC生成位置无关代码,再用-shared生成动态库。
cpp
# 编译位置无关的目标文件
gcc -c -fPIC add.c -o add.o
生成动态库
gcc -shared add.o -o libadd.so
链接动态库
gcc main.c -L. -ladd -o main
- 优点 :可执行文件体积小 ;多个程序可共享同一份库 ,节省内存;库升级时无需重新编译主程序。
- 缺点 :运行时依赖库文件存在,缺失会报错;部署时需要一并携带库文件。
3.3 静态库与动态库对比
| 对比项 | 静态库 | 动态库 |
|---|---|---|
| 链接时机 | 编译链接阶段 | 程序运行时 |
| Linux 后缀 | .a |
.so |
| Windows 后缀 | .lib |
.dll |
| 可执行文件体积 | 较大 | 较小 |
| 运行时依赖 | 不依赖库文件 | 依赖库文件 |
| 库升级 | 需重新编译链接 | 替换库文件即可 |
补充:链接时若同时存在同名静态库和动态库,gcc 默认优先选择动态库 ;可用
-static强制全部使用静态链接。
code>.dll动态库、.lib静态库
| 项目 | 动态链接(默认) | 静态链接(-static) |
|---|---|---|
| 命令 | gcc test.c -o test |
gcc test.c -o test_static -static |
| 原理 | 编译时只记录库符号;程序运行时才加载.so 共享库 | 链接阶段,把用到的库代码直接拷贝进可执行文件 |
| 程序体积 | 小 | 大 |
| 依赖关系 | 运行环境必须存在对应的.so库;库丢失程序无法启动 |
打包后独立,不依赖外部库,移植方便 |
| 资源占用 | 多个程序共享同一份库,节省内存磁盘 | 每个程序自带一份库代码,存在代码冗余 |
| 查看命令 | ldd test 查看依赖的动态库 |
file test_static 显示 statically linked;ldd 提示不是动态程序 |
⚠️ 注意:很多精简系统默认没有安装静态库,加
-static会报cannot find -lc,Ubuntu 需要安装libc6-dev-static
四、高频配套查看命令
file 文件名:识别文件类型(源文件 / 目标文件 / 静态链接程序 / 动态链接程序)ldd 可执行文件:查看程序依赖哪些动态共享库which gcc:查看 gcc 编译器所在路径- 系统文件位置
- 系统头文件:
/usr/include/(stdio.h 等) - 系统库文件:
/lib64/
- 系统头文件:
五、头文件防止重复包含(配套)
头文件被多次#include会重复定义,两种保护写法:
cpp
// 写法1:条件编译(兼容所有编译器)
#ifndef HEADER_NAME_H
#define HEADER_NAME_H
// 头文件内容
#endif
// 写法2:#pragma once(简洁,部分老编译器不支持)
#pragma once
六、常用 gcc 选项速查表
| 参数 | 作用 |
|---|---|
-E |
只做预处理,输出.i |
-S |
预处理 + 编译,输出.s 汇编 |
-c |
预处理 + 编译 + 汇编,输出.o 目标文件,不链接 |
-o xxx |
指定输出文件名 |
-static |
强制静态链接 |
-g |
加入调试信息,给 gdb 调试用 |
-O0 / -O1 / -O2 / -O3 |
优化等级,O0 不优化,O3 优化最强 |
-Wall |
开启全部警告,推荐写代码带上 |
七、易混易错点
.o目标文件不能直接执行:缺少符号地址的重定位,必须经过链接- gcc 默认动态链接,不是静态链接
- 头文件只做声明,函数实现放在.c 源文件;链接阶段才找函数实现
- 预处理只是文本替换 ,不做语法检查,语法错误在
-S编译阶段才报错
八、最简实操串
# 分步全套
gcc -E test.c -o test.i
gcc -S test.i -o test.s
gcc -c test.s -o test.o
gcc test.o -o test
静态链接版本
gcc test.c -o test_static -static
一键编译+开启警告
gcc -Wall test.c -o test