[干货] gdb使用指南
忽然发现自己欠缺点调试能力,所以就学习了一下,来写了篇gdb使用指南,以作备用。
Before we start
1.gdb是啥?
是一个UNIX及UNIX-like下的调试工具。使用gdb,需要在cmd命令窗口下操作。
2.为啥要用gdb?
有的时候静态查错,输出中间变量调试信息,devcpp自带的debug都不好使,就得用gdb。
3.gdb好在哪?
据zhx julao说,熟练使用gdb可以在5min内准确找出代码错误。
1.准备工作
首先我们需要配置好系统的环境变量。如果电脑是在Linux环境下,那么第1部分所有工作都已经做好了,可直接跳过。此部分只针对windows系统,且Win7Win10相同。
我们的编译器,大多都是使用devcpp自带的MinGW32/MinGW64。现在我们要找到它。
打开C盘,进入Programs(x86),找到Dev-Cpp文件夹,可以发现里面有一个MinGW32/MinGW64(这个是根据电脑是32位还是64位而定)文件夹,点进去找到bin。
打开bin(垃圾桶)文件夹,我们惊奇地发现所有编译器都安安静静地待在里面(gdb也在呢!)。然后,我们需要复制现在所处的这个位置的地址。
退回到桌面,找到“我的电脑”或者“计算机”,右键进入计算机属性界面,找到“高级系统设置”。
然后在高级系统设置里找到环境变量,新建用户变量。
变量名:大写PATH,变量值:刚才复制下来的bin文件夹地址。
一路确定出去就配好了,不要点取消。
2.检查程序是否CE
我写了一段把0~100之间的整数二进制输出的代码,叫1.cpp。接下来我们就调试它。
#include<cstdio>
#include<cstring>
using namespace std;
int a[100];
void print(int x)
{
if(!x)
{
printf("0\n");
return;
}
int i,j=0;
while(x)
{
a[++j]=x&1;
x>>=1;
}
for(i=j;i>=1;i--)
printf("%d",a[i]);
printf("\n");
return;
}
int main()
{
int i;
for(i=0;i<=100;i++)
print(i);
return 0;
}
检查是否CE,我们不要用devcpp,因为有一些库devcpp会自动补全,会造成问题。
我们用cmd指令来编译。
打开1.cpp(我们要调试的代码)的目录,地址栏输入cmd,回车。
我们接下来所有工作都是在这个命令窗口里操作。
输入编译命令g++ 1.cpp -o 1.exe
,意思是把1.cpp用g++编译成1.exe。
如果回车之后什么事也没有,那么恭喜,编译成功。
如果提示找不到g++,那么环境变量配错了,再回去配一次。
如果弹出编译信息error,那么是CE了。warning则没有CE。
上图是CE的状况。
如果考试题目pdf的第一页里有编译选项,那就在刚才的指令的后面加上编译选项的全文,比如——
检查完代码,没有CE,我们终于可以开始调试了。
3.简单的调试指令
首先要进入调试工具gdb。我们要重新编译代码,在编译选项的后面加上指令-g
。比如,g++ 1.cpp -o 1.exe -O2 -g
。编译完成之后,键入指令gdb 1.exe
。
P.S.上图中只需要打一遍g++ 1.cpp -o 1.exe -O2 -g
即可,不用编译两次qwq~
然后会出来这么一大堆东西。
我们不管别的,只看倒数第二行红线画出来的东西。如果显示reading...done,那么表示我们的代码被gdb读取成功了,可以开始调试了。
下面我们开始调试吧。
1)运行整段代码:r
敲进去指令r
,我们看到gdb把整段代码运行了一遍,还把输出给了我们。如果不想看到这些输出,可以用文件。
2)在某行设断点:b 1
(在第一行设断点,其他以此类推)
有的时候我们需要让程序在某一行停下来,这个停下来的位置就叫断点。在第1行设断点,指令叫做b 1
。
设好所有断点,然后再敲r
开始运行。
假如我们在第25行设断点,那么程序会执行完第25行,停在第26行,如下图。
3)在某个函数设断点:b print
(在print()函数设断点,其他以此类推)
设好函数断点之后,会在刚刚进入函数的时候停下来,如下图。
4)条件断点:b 27 if (i==10)
(在i==10时在第27行停下来,其他以此类推)
if里面的东西,语法和c++一样。
效果基本和行断点一样qwq。
但是这次是还没有执行第10遍循环就停了下来。
5)一步一步执行:s
比如我们就从刚才停下来的位置开始一步一步执行:
可以看到,每一次都只执行了一句话,而且所有的地方都会被展示出来——例如进出函数,if语句执行与否,等等。
但是这样做会出现问题,比如如果我们继续执行下去——
再按十几次s之后,就出现上图一大堆看不懂的东西了。
什么情况?
其实是程序进到了printf()函数的里面,所以让人看不懂了的。
那怎么避免这种情况嘞?
6)一步执行完当前函数:finish
敲finish
,就直接退回到我们的代码里了。
如果本来就不想进到函数里面呢?
7)执行下一步,如果是函数一步执行完:n
直接敲n,可以看到程序没有进入printf函数,如下图——
8)继续不断执行,直到下一个断点:c
我们先设上下一个断点,然后敲c
。
可以看到程序执行完86停下,87还没执行。
9)打印某个变量的值:p i
(打印i的值,其余以此类推)
很简单。注意,如果是递归的话,只显示当前层上的值。
10)持续跟踪某个变量的值:display i
(持续跟踪i,其余以此类推)
也很简单。敲完display i
之后,执行s
,n
等等都可以。
11)退出gdb:q
有时候gdb还会不舍地和你挽留一句qwq:
4.测试时间和内存
1)测试内存
首先还是编译代码,要加上-g
。然后,输入size 1.exe
。
显示的第三个和第四个数(计算方式有微小的差异)就是以B为单位的运行内存。只要保证这两个数都不MLE,代码一定安全。
2)测试时间
首先还是编译代码,要加上-pg
,注意了,是-pg
,因为我们使用的工具变了,叫gprof。
编译完之后,直接输入1.exe
,意思是在cmd里运行一遍1.exe。
然后,我们整个程序所有函数的运行时间信息都被gprof记下来了。现在我们要导出这些数据,用的是指令gprof 1.exe > test.out
,意思是把信息导出到test.out里。
打开文件test.out。
所有的运行时间信息都在里面了!
注:如果函数里面只有一句话,c++会把它优化掉,所以这个函数执行时间为0。
End
gdb大法好!
也许它现在看起来比较麻烦,但是我尽量还是坚持zhx学长的建议,常用gdb吧。
祝:NOIP CSP rp++
完结撒花!
orz~