前言:
GDB(GNU Debugger)是一个用来调试C/C++程序的功能强大的调试器,是Linux系统开发 C/C++最常用的调试器
程序员可以使用GDB来跟踪程序中的错误,从而减少程序员的工作量。
Linux 开发C/C++ 一定要熟悉 GDB
VSCode是通过调用GDB调试器来实现C/C++的调试工作的;
Windows 系统中,常见的集成开发环境(IDE),如 VS、VC等,它们内部已经嵌套了相应的调试
GDB主要功能:
设置断点(断点可以是条件表达式)
使程序在指定的代码行上暂停执行,便于观察
单步执行程序,便于调试
查看程序中变量值的变化
动态改变程序的执行环境
分析崩溃程序产生的core文件
4.1 常用调试命令参数
调试开始:执行gdb [exefilename] ,进入gdb调试程序,其中exefilename为要调试的可执行文件名
$(gdb)help(h)
$(gdb)run(r)
件)
$(gdb)start
$(gdb)list(l)
数)
$(gdb)set
$(gdb)next(n)
$(gdb)step(s) # 查看命令帮助,具体命令查询在gdb中输入help + 命令
# 重新开始运行文件( run-text:加载文本文件, run-bin:加载二进制文
# 单步执行,运行程序,停在第一行执行语句
# 查看原代码(list-n,从第n行开始查看代码。 list+ 函数名:查看具体函
# 设置变量的值
# 单步调试(逐过程,函数直接执行)
# 单步调试(逐语句:跳入自定义函数内部执行)
$(gdb)backtrace(bt) # 查看函数的调用的栈帧和层级关系
$(gdb)frame(f) # 切换函数的栈帧
$(gdb)info(i) # 查看函数内部局部变量的数值
$(gdb)finish # 结束当前函数,返回到函数调用点
$(gdb)continue(c) # 继续运行
$(gdb)print(p) # 打印值及地址
$(gdb)quit(q) # 退出gdb
$(gdb)break+num(b)
$(gdb)info breakpoints
$(gdb)delete breakpoints
$(gdb)display
$(gdb)undisplay
$(gdb)watch
$(gdb)i watch
$(gdb)enable breakpoints
$(gdb)disable breakpoints
$(gdb)x
$(gdb)run argv[1] argv[2] # 在第num行设置断点
# 查看当前设置的所有断点
num(d) # 删除第num个断点
# 追踪查看具体变量值
# 取消追踪观察变量
# 被设置观察点的变量发生修改时,打印显示
# 显示观察点
# 启用断点
# 禁用断点
# 查看内存x/20xw 显示20个单元, 16进制, 4字节每单元
# 调试时命令行传参
$(gdb)set follow-fork-mode child#Makefile项目管理:选择跟踪父子进程(fork())
Tips:
- 编译程序时需要加上-g,之后才能用gdb进行调试: gcc -g main.c -o main
- 回车键:重复上一命令
4.2 【实战】命令行调试
给出一段简单代码,准备调试。
#include <iostream>
using namespace std;
int main(int argc,char **argv)
{
int N = 100;
int sum = 0;
int i = 1;
// calculate sum from 1 to 100
while (i <= N)
{
sum = sum + i;
i = i + 1;
}
cout << "sum = " << sum << endl;
cout << "The program is over." << endl;
return 0;
}
gdb调试过程
b main
在main函数起始处设置断点
vim 显示行号 :set nu
用gdb调试程序的bug
greet.c
#include<stdio.h>
int display1(char *string);
int display2(char *string);
int main()
{
char string[] = "Embedded Linux";
display1 (string);
display2 (string);
}
int display1 (char *string)
{
printf ("The original string is %s \n",string);//%s表示字符串的占位符,display1直接输出string
}
int display2 (char *string1)//变量不重名
{
char *string2;/*一个字符型的指针(指针就是内存地址)。所有实际数据类型,不管是整型、浮点型、字符型,
还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的长的十六进制数。
不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。*/
int size,i;
size = strlen (string1);/*strlen是一个函数,sizeof是一个单目运算符。
strlen 它用来计算指定字符串 str 的长度,但不包括结束字符(即 null 字符)。
关键字 sizeof 是一个单目运算符,参数可以是数组、指针、类型、对象、函数等
strlen使用时注意事项:
strlen只能用char*做参数,且必须是以''\0''结尾的。换句话说:strlen只能计算字符串的长度。
sizeof使用时注意事项:
1 对于直接的调用sizeof,如果是数组首地址,sizeof会输出数组所占地址空间的大小(字节为单位)。
如果是一个指针的话,则输出在该系统中地址的字节宽度,即(位宽/8)。*/
string2 = (char *) malloc (size + 1);/*如果要存储整个学校的人数时,会出现内存不够用的情况;
当我们开辟全校人数大小的数组时,输入一个班人数的大小时,会出现内存浪费的情况。
为了应对上述问题,我们引入malloc函数。
malloc是动态内存分配函数,用于申请一块连续的指定大小的内存块区域以void*类型返回分配的内存区域地址
malloc函数原型
extern void *malloc(unsigned int num_bytes);
意为分配长度为num_bytes字节的内存块
头文件#include<malloc.h>
malloc函数返回值
如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
malloc函数使用注意事项
malloc函数的返回的是无类型指针,在使用时一定要强制转换为所需要的类型。
**(敲黑板)重点:在使用malloc开辟空间时,使用完成一定要释放空间,如果不释放会造内存泄漏。
在使用malloc函数开辟的空间中,不要进行指针的移动,
因为一旦移动之后可能出现申请的空间和释放空间大小的不匹配
使用形式
malloc只开辟空间,不进行类型检查,只是在使用的时候进行类型的强转。
mallo函数返回的实际是一个无类型指针,必须在其前面加上指针类型强制转换才可以使用
在使用malloc函数之前我们一定要计算字节数,malloc开辟的是用户所需求的字节数大小的空间。
如果多次申请空间那么系统是如何做到空间的不重复使用呢?
在使用malloc开辟一段空间之后,系统会在这段空间之前做一个标记(0或1),
当malloc函数开辟空间如果遇到标记为0就在此开辟,如果为1说明此空间正在被使用。
free函数
作用:释放malloc(或calloc、realloc)函数给指针变量分配的内存空间。
注意:使用后该指针变量一定要重新指向NULL,防止悬空指针(失效指针)出现,有效规避错误操作。
free函数在释放空间之后,把内存前的标志变为0,且为了防止数据泄露,它会把所释放的空间用cd进行填充。
*/
for (i = 0; i < size; i++)
{
string2[size - i] = string1[i];/*在for循环的作用下,string1的头依次复制给string2的尾
string1的第一个给string2的最后一个,以此*/
}
string2[size+1] = ' ';//在string2的尾加个空字符
printf("The string afterward is %s\n",string2);
}
先在vim里用:set nu显示行号
再在 for 和print处打断点
b 62
b 68
发现string2[0]没有赋值
修改string2[size - i] = string1[i]
为string2[size - i - 1] = string1[i]
后
能翻转字符串