第一部分:GDB 调试详解
GDB 是 Linux 下功能最强的命令行调试器,支持断点、单步执行、查看内存/变量、调试核心转储(core dump)、多线程调试等功能。使用 GDB 前需确保程序编译时保留调试信息(-g 选项)。
一、GDB 准备工作
1. 编译带调试信息的程序
C++ 程序需用 g++ 编译时添加 -g 选项(保留变量名、行号等调试信息,不影响程序功能):
bash
g++ -g -o test test.cpp # 生成带调试信息的可执行文件 test
-
若需优化编译(如
-O2),可同时加-g(优化不影响调试核心功能):bashg++ -g -O2 -o test test.cpp
2. GDB 启动方式
bash
gdb ./test # 直接启动调试 test 程序
gdb ./test core # 调试程序崩溃生成的 core 文件(需先开启 core 生成)
gdb -p 1234 # 附加到运行中的进程(PID=1234)
二、GDB 核心调试命令(含示例)
假设我们有如下测试程序 test.cpp,用于演示调试流程:
cpp
#include <iostream>
#include <vector>
using namespace std;
int add(int a, int b) {
int c = a + b;
return c; // 行号 6
}
int main() {
int x = 10, y = 20;
vector<int> vec = {1,2,3};
int res = add(x, y); // 行号 12
cout << "res: " << res << endl;
vec.push_back(4);
cout << "vec size: " << vec.size() << endl; // 行号 16
return 0;
}
1. 基础调试流程
| 命令 | 功能描述 | 示例与效果 |
|---|---|---|
run [args] / r |
启动程序(可带命令行参数) | r → 运行 test 程序,直到结束或断点 |
break [位置] / b |
设置断点(位置:行号、函数名、文件名:行号) | b 12 → 第12行设断点;b add → 函数add设断点;b test.cpp:6 → 跨文件断点 |
info breakpoints / i b |
查看所有断点状态 | 显示断点编号、位置、命中次数 |
delete [断点号] / d |
删除断点(无编号则删除所有) | d 1 → 删除编号为1的断点 |
disable/enable [断点号] |
禁用/启用断点 | disable 1 → 禁用断点1(不删除) |
next / n |
单步执行(跳过函数调用,"逐行") | 执行第12行时,n 直接得到 res 结果 |
step / s |
单步执行(进入函数调用,"逐语句") | 执行第12行时,s 进入 add 函数(跳至行6) |
finish / f |
执行完当前函数并返回上一层 | 在 add 函数内执行 f,返回 main 函数第12行 |
continue / c |
从当前位置继续运行(直到下一个断点或结束) | 命中断点后,c 继续执行 |
print [变量/表达式] / p |
查看变量值或表达式结果 | p x → 打印 x=10;p vec[0] → 打印向量第一个元素;p add(5,6) → 执行函数并打印结果 |
display [变量] |
自动显示变量值(每次单步后输出) | display res → 每次执行后显示 res 的值 |
undisplay [编号] |
取消自动显示 | undisplay 1 → 取消编号1的自动显示 |
backtrace / bt |
查看函数调用栈(崩溃时定位问题核心) | 程序崩溃时,bt 显示从 main 到崩溃点的调用链 |
frame [栈帧号] / f |
切换到指定栈帧(查看上层函数的变量) | bt 显示栈帧0(当前函数)、1(上一层),f 1 切换到上一层 |
quit / q |
退出 GDB |
2. 进阶调试技巧
(1)条件断点
仅当满足特定条件时断点生效,适合循环、分支中的问题:
bash
b 12 if x > 15 # 第12行仅当 x>15 时中断
b add if a == 10 # 函数 add 仅当参数 a=10 时中断
(2)监控变量变化(watch 命令)
当变量被修改或读取时中断,用于定位变量被意外篡改的问题:
bash
watch x # 写监控:x 被修改时中断(最常用)
rwatch x # 读监控:x 被读取时中断
awatch x # 读写监控:x 被读或写时中断
示例:在 main 函数中 watch vec.size(),执行 vec.push_back(4) 时会触发中断。
(3)调试核心转储(core dump)
程序崩溃时(如段错误 Segmentation fault),系统会生成 core 文件(记录崩溃时的内存、寄存器状态),通过 GDB 分析 core 可快速定位崩溃点。
-
步骤1:开启 core 文件生成(默认关闭)
bashulimit -c unlimited # 临时开启(当前终端有效),生成 core 文件无大小限制 # 永久开启:编辑 /etc/security/limits.conf,添加以下两行(需重启) # * soft core unlimited # * hard core unlimited -
步骤2:触发崩溃并生成 core
例如程序存在数组越界:
cppint main() { int arr[3] = {1,2,3}; cout << arr[10] << endl; // 越界访问,触发段错误 return 0; }运行程序后会生成
core文件(或core.1234,1234为PID):bash./test Segmentation fault (core dumped) # 生成 core 文件 -
步骤3:用 GDB 分析 core
bashgdb ./test core # 加载程序和 core 文件 (gdb) bt # 查看调用栈,直接定位到越界的行号
(4)C++ 特有调试
-
查看类对象成员:
p obj.member(需确保对象未被析构)cppclass Person { public: int age; string name; }; Person p = {20, "Tom"};GDB 中:
p p.age→ 20;p p.name→ "Tom"(需#include <string>)。 -
调试 STL 容器(vector、map 等):GDB 对 STL 支持有限,可通过
print查看元素,或安装libstdc++-dbg增强支持:bashsudo apt install libstdc++6-dbg # 安装 STL 调试库示例:
p vec→ 显示 vector 的大小、容量和元素;p vec[2]→ 查看第3个元素。 -
解函数名修饰(C++ 编译会修饰函数名):GDB 自动解修饰,若需手动:
bash(gdb) demangle _Z3addii # 解修饰函数名,输出 add(int, int)
(5)多线程调试
info threads:查看所有线程(编号、状态、所属进程)。thread [线程号]:切换到指定线程。break [位置] thread [线程号]:给指定线程设断点。set scheduler-locking on:锁定当前线程执行(防止其他线程干扰,调试单线程逻辑时常用)。thread apply [线程号] bt:查看指定线程的调用栈(如thread apply all bt查看所有线程栈)。
三、GDB 常见问题
- 无法设置断点:程序未加
-g编译,重新编译时添加-g。 - 变量显示
optimized out:编译时优化级别过高(如-O3),调试时用-O0或-O1。 - 无法生成 core 文件:未开启
ulimit -c unlimited,或目录无写权限。
第二部分:内存泄漏检测详解
内存泄漏是指动态分配的内存(new/malloc)未通过 delete/free 释放,导致内存持续占用,长期运行会耗尽系统内存。Linux 下常用检测工具:Valgrind(memcheck) 、AddressSanitizer(ASAN) 、mtrace 等。
内存泄漏示例
先写一个存在内存泄漏的程序 leak.cpp:
cpp
#include <iostream>
using namespace std;
void func() {
int* p = new int[10]; // 动态分配数组,未释放
p[0] = 100;
// 无 delete[] p; 导致内存泄漏
}
int main() {
func();
cout << "程序结束" << endl;
return 0;
}
一、Valgrind(memcheck):最常用的内存检测工具
Valgrind 是开源工具集,核心工具 memcheck 可检测内存泄漏、内存越界、使用已释放内存等问题。无需修改代码,无需重新编译(但加 -g 可定位行号)。
1. 安装 Valgrind
bash
sudo apt update && sudo apt install valgrind
2. 使用步骤
(1)编译程序(建议加 -g 保留行号):
bash
g++ -g -o leak leak.cpp
(2)用 Valgrind 运行程序:
bash
valgrind --leak-check=full ./leak # --leak-check=full 开启全面泄漏检测
3. 输出结果解读
核心输出片段:
==12345== Memcheck, a memory error detector
==12345== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==12345== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==12345== Command: ./leak
==12345==
程序结束
==12345==
==12345== HEAP SUMMARY:
==12345== in use at exit: 40 bytes in 1 block
==12345== total heap usage: 2 allocs, 1 frees, 72,744 bytes allocated
==12345==
==12345== 40 bytes in 1 block are definitely lost in loss record 1 of 1
==12345== at 0x4848899: operator new[](unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==12345== by 0x1091C2: func() (leak.cpp:5) # 明确指出泄漏发生在 leak.cpp 第5行
==12345== by 0x1091E6: main (leak.cpp:12) # 调用链
==12345==
==12345== LEAK SUMMARY:
==12345== definitely lost: 40 bytes in 1 block # 确认泄漏(必须修复)
==12345== indirectly lost: 0 bytes in 0 blocks
==12345== possibly lost: 0 bytes in 0 blocks
==12345== still reachable: 0 bytes in 0 blocks
==12345== suppressed: 0 bytes in 0 blocks
- 泄漏类型说明:
definitely lost:确认泄漏(无任何指针指向该内存,必须修复)。indirectly lost:间接泄漏(泄漏的内存是另一个泄漏内存的子对象)。possibly lost:可能泄漏(存在指针指向该内存,但不确定是否有效)。still reachable:内存未释放但仍可访问(如全局变量分配的内存,不一定是泄漏)。
4. 进阶选项
--show-leak-kinds=all:显示所有类型的泄漏(默认仅显示definitely lost等)。--track-origins=yes:追踪内存分配的原始位置(适合定位野指针问题)。--log-file=leak.log:将输出保存到文件(避免终端刷屏)。
5. 优缺点
- 优点:检测全面(支持泄漏、越界、使用已释放内存等)、无需修改代码。
- 缺点:运行速度慢(约为原程序的 10-50 倍)、内存占用高(约为原程序的 2-4 倍),不适合生产环境,适合测试阶段。
二、AddressSanitizer(ASAN):快速轻量的内存检测
ASAN 是 GCC/Clang 内置的内存错误检测工具(需 GCC 4.8+ 或 Clang 3.1+),编译时添加选项即可,支持检测内存泄漏、越界、使用已释放内存、栈溢出等问题。运行速度比 Valgrind 快(约为原程序的 2-5 倍),内存占用约为原程序的 2 倍,适合开发阶段实时检测。
1. 使用步骤
(1)编译程序时添加 ASAN 选项:
bash
g++ -g -fsanitize=address -o leak_asan leak.cpp # -fsanitize=address 启用 ASAN
- 若用 Clang:
clang++ -g -fsanitize=address -o leak_asan leak.cpp。
(2)直接运行程序:
bash
./leak_asan
2. 输出结果解读
程序运行结束后,ASAN 会自动检测内存泄漏并输出:
程序结束
=================================================================
==12346==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 40 byte(s) in 1 object(s) allocated from:
#0 0x7f1234567890 in operator new[](unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.6+0xb0890)
#1 0x556789abcdef in func() /home/user/leak.cpp:5
#2 0x556789abd20 in main /home/user/leak.cpp:12
#3 0x7f1234123456 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x28083)
#4 0x556789abcb9 in _start (/home/user/leak_asan+0xcb9)
SUMMARY: AddressSanitizer: 40 byte(s) leaked in 1 allocation(s).
- 直接指出泄漏的内存大小、位置(
leak.cpp:5)和调用链,可读性强。
3. 进阶选项
export ASAN_OPTIONS=detect_leaks=0:临时禁用泄漏检测(仅检测其他内存错误)。export ASAN_OPTIONS=log_path=asan.log:将输出保存到文件。export ASAN_OPTIONS=fast_unwind_on_malloc=0:启用精确栈回溯(默认快速回溯可能不准确)。
4. 优缺点
- 优点:运行速度快、检测能力强(支持多种内存错误)、无需额外工具(编译器内置)。
- 缺点:需要重新编译程序、不支持部分老编译器、内存占用略高于普通程序。
三、mtrace:轻量的 malloc 跟踪工具
mtrace 是 glibc 提供的简单内存泄漏检测工具,通过跟踪 malloc/free 调用实现,需要修改代码,适合简单程序或嵌入式环境(无 Valgrind/ASAN 时)。
1. 使用步骤
(1)修改代码,添加 mtrace 头文件和跟踪函数:
cpp
#include <iostream>
#include <mcheck.h> // 包含 mtrace 头文件
using namespace std;
void func() {
int* p = new int[10];
p[0] = 100;
}
int main() {
mtrace(); // 启动内存跟踪
func();
cout << "程序结束" << endl;
muntrace(); // 停止内存跟踪
return 0;
}
(2)编译程序(加 -g 保留行号):
bash
g++ -g -o leak_mtrace leak.cpp
(3)设置跟踪日志文件,运行程序:
bash
export MALLOC_TRACE=leak.log # 指定日志文件
./leak_mtrace # 运行程序,生成 leak.log
(4)用 mtrace 分析日志:
bash
mtrace ./leak_mtrace leak.log # 第一个参数是可执行文件,第二个是日志文件
5. 输出结果解读
Memory not freed:
-----------------
Address Size Caller
0x000055f8a7a2aeb0 40 at /home/user/leak.cpp:5
- 显示未释放的内存地址、大小和分配位置(
leak.cpp:5)。
6. 优缺点
- 优点:轻量(几乎不影响程序性能)、无需额外依赖(glibc 自带)。
- 缺点:需修改代码、仅支持
malloc/free(对 C++new/delete支持有限,需确保new底层调用malloc)、检测功能单一(仅泄漏)。
四、其他工具
-
cppcheck :静态代码分析工具(不运行程序),可检测潜在内存泄漏(如
new未对应delete):bashsudo apt install cppcheck cppcheck --enable=all leak.cpp # 检测泄漏和其他代码问题 -
Dr.Memory:跨平台内存检测工具(类似 Valgrind),支持 Windows/Linux,对 C++ 支持较好。
-
Intel Inspector:商业工具,功能强大,适合大型项目(免费试用)。
五、内存泄漏预防措施
-
优先使用智能指针(
std::unique_ptr、std::shared_ptr),自动管理内存生命周期,避免手动new/delete:cpp#include <memory> void func() { auto p = std::make_unique<int[]>(10); // 自动释放,无泄漏 p[0] = 100; } -
避免循环引用(
std::shared_ptr循环引用会导致泄漏,需用std::weak_ptr打破)。 -
统一内存分配/释放方式(如用
new[]分配则用delete[]释放,避免混用malloc/delete)。 -
开发阶段用 ASAN 实时检测,测试阶段用 Valgrind 全面扫描。
总结
| 工具/功能 | GDB 调试 | Valgrind | AddressSanitizer | mtrace |
|---|---|---|---|---|
| 核心用途 | 定位崩溃、逻辑错误 | 全面内存检测(泄漏+错误) | 快速内存检测(泄漏+错误) | 简单内存泄漏检测 |
| 编译要求 | 需 -g 选项 |
无(加 -g 更好) |
需 -fsanitize=address |
需 -g 选项 |
| 运行速度 | 接近原程序 | 慢(10-50倍) | 较快(2-5倍) | 接近原程序 |
| 适用场景 | 开发/调试阶段 | 测试阶段 | 开发/测试阶段 | 简单程序/嵌入式环境 |
最佳实践:
- 开发 C++ 程序时,编译时加
-g -fsanitize=address,实时检测内存错误和泄漏。 - 程序崩溃时,开启 core dump,用 GDB 分析
core文件定位崩溃点。 - 测试阶段,用 Valgrind 进行全面内存扫描,确保无泄漏。
- 编码时优先使用智能指针,从源头减少内存泄漏风险。