GDB 是C++后端开发、底层调试的核心武器。
一、GDB 是什么?
GDB 全称 GNU Debugger ,是Linux/Unix平台下免费、开源、功能最强的程序调试器,完美支持 C/C++、Go、Rust 等编译型语言。
它的核心能力:
- 控制程序的启动/暂停/单步执行
- 查看程序运行时的变量、内存、调用栈
- 断点调试、条件触发、内存观察
- 定位崩溃(段错误)、死锁、逻辑错误
- 调试运行中的进程、core转储文件(线上排查必备)
二、前置准备:编译带调试信息的程序
GDB 无法调试纯发行版程序,编译时必须添加调试符号!
基础编译命令(C++)
bash
# -g:生成标准调试信息
# -O0:关闭编译器优化(优化会打乱代码执行顺序,导致调试异常)
g++ -g -O0 main.cpp -o my_program
进阶推荐
bash
# -ggdb:生成GDB专属调试信息,功能更全
g++ -ggdb -O0 main.cpp -o my_program
⚠️ 避坑:生产环境可以关闭调试符号,但测试/调试环境必须加 -g 且禁止优化。
三、GDB 三种启动方式
1. 直接调试可执行文件
bash
gdb ./my_program
启动后进入 GDB 交互模式,提示符:(gdb)
2. 调试崩溃后的 core 文件(线上崩溃排查)
程序崩溃后会生成 core.xxx 文件,直接复盘崩溃现场:
bash
# 先开启core文件生成(执行一次即可)
ulimit -c unlimited
# 调试core文件
gdb ./my_program core.12345
3. 附加到运行中的进程(调试在线服务)
程序已经在运行,无需重启,直接附加调试:
bash
# 先查进程PID
ps -ef | grep my_program
# 附加到进程
gdb -p 12345
四、GDB 基础核心命令
用一个简单的C++示例程序演示:
cpp
// main.cpp
#include <iostream>
#include <vector>
using namespace std;
int add(int a, int b) {
return a + b;
}
int main() {
int x = 10, y = 20;
int res = add(x, y);
cout << res << endl;
vector<int> vec = {1,2,3};
return 0;
}
1. 程序控制命令
| 命令 | 简写 | 作用 |
|---|---|---|
run |
r |
启动程序(从头运行) |
start |
- | 启动程序,暂停在main函数第一行 |
continue |
c |
继续运行程序,直到下一个断点/崩溃 |
next |
n |
单步执行(不进入函数内部,逐行跑) |
step |
s |
单步执行(进入函数内部,比如进入add函数) |
quit |
q |
退出GDB |
2. 断点管理(调试的核心)
断点让程序在指定位置暂停,方便排查问题:
gdb
# 1. 在指定行打断点
break 10 # b 10 → 暂停在第10行
break main # b main → 暂停在main函数入口
break add # 暂停在add函数入口
# 2. 条件断点(进阶)
break 10 if x==10 # 只有x=10时才触发断点
# 3. 查看所有断点
info breakpoints # i b
# 4. 删除断点
delete 1 # 删除编号为1的断点
delete # 删除所有断点
3. 查看代码上下文
gdb
list # l → 显示当前行前后10行代码
list 1,20 # 显示1-20行代码
list add # 显示add函数的代码
4. 查看变量/内存(排查数值错误)
gdb
# 打印变量
print x # p x → 打印变量x的值
print res # 打印计算结果
# 打印C++对象/容器
print vec # 打印vector对象
print vec[0] # 打印容器元素
# 格式化打印
print /x x # 以十六进制打印
print /d x # 以十进制打印
# 修改变量值(调试神器)
set var x=100 # 把x的值改成100,测试逻辑
5. 栈回溯(定位崩溃/调用链)
这是定位段错误的第一命令
gdb
bt # 查看函数调用栈(崩溃时看哪一行出错)
bt full # 查看完整调用栈 + 所有局部变量(最强)
frame 0 # 切换到指定栈帧
五、实战:C++ 段错误(Segmentation fault)排查
段错误是C++最常见的崩溃(空指针、野指针、数组越界),我们用GDB快速定位:
测试代码(故意制造空指针)
cpp
#include <iostream>
using namespace std;
void test() {
int* p = nullptr; // 空指针
*p = 10; // 非法访问内存 → 段错误
}
int main() {
test();
return 0;
}
调试流程
- 编译:
g++ -g -O0 main.cpp -o crash - 启动GDB:
gdb ./crash - 运行程序:
(gdb) run - 程序崩溃,执行:
(gdb) bt full - 查看崩溃代码:
(gdb) list - 查看可疑指针:
(gdb) print p
输出结果 :直接看到p = 0x0(空指针),崩溃行一目了然。
六、C++ 专属调试技巧
GDB 对C++的类、对象、虚函数、STL、多线程有专属支持,这是区别于C语言调试的关键:
1. 调试C++ 类与对象
cpp
class Test {
public:
int num = 0;
void func() { num = 100; }
};
gdb
# 打印对象成员变量
print test_obj.num
# 调用对象成员函数
print test_obj.func()
# 调试虚函数
print *virtual_ptr # 查看虚表指针
2. 调试 STL 容器(vector/map/string)
默认GDB打印STL会很冗长,推荐直接访问元素:
gdb
print vec.size()
print vec[0]
print map["key"]
print str.c_str()
3. 多线程调试
gdb
info threads # 查看所有线程
thread 2 # 切换到2号线程
break 10 thread 2 # 仅在2号线程第10行打断点
4. 观察点(监控内存修改)
排查内存被意外篡改的神器:
gdb
watch p # 当指针p的值改变时,程序自动暂停
rwatch p # 当读取p时暂停
awatch p # 读写p时都暂停
七、GDB 高级实用技巧
1. 日志输出
把调试信息保存到文件,方便复盘:
gdb
set logging file gdb.log
set logging on
2. 跳过代码执行
调试时跳过某行错误代码,无需改源码:
gdb
jump 15 # 直接跳转到第15行执行
3. 查看内存原始数据
gdb
x/10i main # 查看main函数的汇编代码
x/10w p # 查看指针p指向的10个内存值
八、注意避坑
- 忘记加 -g 编译:GDB 看不到源码和变量,直接报废
- 开启编译器优化 :
-O2会打乱代码行,单步执行异常 - 混淆 next 和 step :
n不进函数,s进函数,别用错
总结
对于C++来说,GDB 不是可选工具,而是必备技能:
- 基础:
run/break/next/print/bt搞定大多数的调试场景 - 进阶:core文件、多线程、观察点,搞定线上崩溃
- C++专属:类、STL、虚函数调试,贴合实际开发
抛弃低效的cout/printf调试,熟练使用GDB后,排查bug的效率会提升10倍以上。