实用操作流程
要想进行gdb调试,首先要在编译阶段添加-g
然后对运行文件加上前缀gdb,进入gdb调试页面
输入run,运行,会自动显示哪行出现了问题,帮助我们debug
接下来要学习的数据结构涉及到大量的内存空间管理和指针,编写程序时很容易发生内存上的错误,这时候常用的调试工具就是gdb(gun debugger)
常见内存问题
1.内存泄漏
分配的内存没有释放,或者移动了指向其的指针,导致丢失这块内存的地址。最终使得内存无法被回收,程序若持续运行,被调用会导致可用内存越来越少,程序崩溃
void leak() {
int *p = malloc(100);
// 函数结束,p 销毁,但指向的 100 字节未释放,永远泄漏
}
void lost_pointer() {
int *p = malloc(sizeof(int));
p = malloc(sizeof(int)); // 原先的 p 丢失,第一个分配块泄漏
......
free(p);
}
2.越界访问
读写超过数组大小或者分配内存大小的内存区域
void overflow() {
int *arr = malloc(5 * sizeof(int));
for (int i = 0; i <= 5; i++) { // i==5 时越界写入
arr[i] = i;
}
free(arr);
}
3.使用未初始化的数值
void uninit() {
int x;
printf("%d\n", x); // 未初始化,可能触发随机行为
int *p = malloc(sizeof(int));
printf("%d\n", *p); // 动态内存内容也是随机值(未 memset 或赋值)
free(p);
}
4.修改(写入)已释放的内存
内存被释放后指针依旧存有地址且地址是有效的,此时这处内存可能被其他程序调用,此时修改这处内存的值会引发错误
int *p = malloc(sizeof(int));
if (p == NULL) //防御性编程
{
printf("malloc fail");
return -1;
}
*p = 42;
free(p);
*p = 99; // 非法写入
5.对一块内存空间重复释放
int *p = malloc(10);
free(p);
free(p); // 未定义行为,程序通常会直接崩溃
6.栈溢出
递归过深导致栈空间不足
void deep_recursion(int n) {
char big[1000000]; // 每次递归占用大量栈空间
if (n == 0) return;
deep_recursion(n-1);
}
7.野指针
int *p; // 指向的地址是随机的
*p = 10; // 可能破坏已有程序(修改了其他程序正在使用的空间)
8.内存碎片
内存中有很多小的,不连续的内存块,导致总和内存看似足够,其实无法满足一些新的内存申请。
内存管理一般以1024字节为一单位,申请堆区空间时尽量申请其因数或者倍数就可以一定程度上避免该问题
编译时添加后缀 -Wall可以警告你哪些地方有可能出现问题
printf(%s:%s:%d\n",__FILE__,__func__,__LINE__);这个函数可以插入到怀疑有问题的函数附近,可以看到程序运行到哪个文件哪个函数第几行。
gdb调试页面中的基础命令
1.list -- 查看源代码,方便插入断点与调试
2.b 行号 --下断点
3.r -- run --从头开始运行点,到断点处会停,到引发bug的那行也会停
4.p 变量名 -- 查看当前的变量
5.n -- next ---单步调试
6.quit -- 退出
7.`continue` `c` 继续执行直到下个断点(run是从头开始运行)
8.info locals # 显示当前栈帧的局部变量
break function_name //在函数入口设断点,function_name(函数名)
使用x命令可以查看详细内存的值
格式:`x /[个数][格式][大小] 地址`
- 格式:`x`(十六进制), `d`(十进制), `s`(字符串), `i`(指令)
- 大小:`b`(字节), `h`(半字2字节), `w`(字4字节), `g`(8字节)
示例:
(gdb) p arr # 打印指针本身的值,即堆地址 $1 = (int *) 0x5555555592a0
(gdb) x/5dw arr # 以十进制、字为单位,查看该地址开始的5个字 0x5555555592a0: 1 2 3 4 5 (gdb) x/20bx arr # 以十六进制字节,查看20个字节,顺便能够清晰看到小端存储
Tips:配合断点使用
| 命令 | 常用缩写 | 说明 |
| `watch var` | `wa var` | 监视变量变化,`wa` 是 `watch` 唯一前缀 |
| `watch *0x...` | `wa *0x...` | 监视地址内容变化 |
| `rwatch *addr` | `rw *addr` | 读监视点,`rw` 为 `rwatch` 缩写 |
| `awatch *addr` | `aw *addr` | 读写监视点,`aw` 为 `awatch` 缩写 |
读监视点意思是只要程序读取(访问)了某个变量或地址的值,GDB 就会立刻暂停程序。
普通的watch意思是写(改变)了就停,awatch意思是读写都停

2287

被折叠的 条评论
为什么被折叠?



