第二章:数据类型
第四节:调试工具简介
在谈论无符号整数前,我们先要熟悉一个工具,这个工具的名名字叫gdb。一般来说,这个工具我们叫做调试器。现在这么叫,大家可能不明白意思,我这么解释一下吧,这个gdb就像一个盒子,我们把我们编写的程序放到这个盒子里面运行,这个盒子又个神奇的功能,能够控制整个程序的运行,让程序运行,程序就运行,让程序停止运行,他就停止运行,并且还可以指定程序在那个地方停止运行。另外,在程序停止运行期间,我们还可以通过这个工具查看这个程序运行的时候,程序内每个变量当前的值,也就是程序暂停的时候,每个变量的值。而且,还可以看你想看的内存地址中,所保存的二进制串。这个工具牛逼吧。
这个工具咋用呢?我们先编写一下程序:
/*
冒牌程序员-毛哥
*/
/***************debug_1.c**********/
#include<stdio.h>
int main()
{
int x,y;
x=20;
y=30;
printf("x=%d y=%d\n",x,y);
x+=100;
y=0x12345678;
return 0;
}
按照下图中,输入黄色渲染的命令,就会出现如下画面:
看上图中被黄色荧光笔标注的部分,就是我们要输入的命令或者选项。
第一条:
gcc debug_1.c -o 1 -g
这里,gcc是编译命令,debug_1.c是源代码,-o 1是说明生成的可执行文件名字是1。-g说明在生成的可执行文件中,应该包含可执行文件的调试信息。
说明:
包含调试信息这句话对与初学者来说比较抽象,到底啥是调试信息,啥是调试呢?调试其实就是查找程序中的错误,也就是你们常听说的找bug。怎么找呢,就是在程序员认为可能出错的地方,把程序暂停,然后看程序中各个变量或者其他信息,是不是程序执行到这里,按照程序逻辑应该有的信息,如果发现信息不对,就查找原因,这就是调试。为了完成上述目的,需要一些辅助信息。首先,我们可以将程序暂停在源代码中的某一句话上,例如,我们需要将程序暂停到return 0;这句话上(暂停到这句话,其实此时这句话还没有被执行,而这句话的上一句y=0x12345678;已经被执行完毕)。如何暂停呢,我们就使用调试器命令“b n”,其中b是调试命令,n是参数,n代表的是程序应该暂停的行数,也就是行号。那么这个时候,调试器必须知道return 0;这句话,被翻译成了那些机器执行,当执行到这些机器指令前,必须暂停。这个机器指令和语言源代码对应关系信息是不是调试器必须知道呢?肯定是必须的,这些信息对于不需要调试的程序是否有价值呢,没有,因为正常程序不会被暂停。那么这个二进制指令和源代码对应关系信息就是调试信息,-g编译选项就是要编译器生成这些信息,然后把这些信息以一定的格式,保存在可执行文件中。gdb调试器在调试可执行程序的时候,就可以使用这些信息了。当然,调试信息不只包含源代码和二进制指令对应关系,还有其他很多东西,专门有技术标准进行规定。
第二条:
gdb 1
这里gdb是调试器启动命令,后面的1是参数,代表要调试的目标程序。
第三条:
start
这个其实已经是一条调试器的指令了,表示被调试程序开始运行。
第四条:
n
程序开始调试的时候,调试器会检测到可执行程序1中,有些机器指令没有对应的调试信息,此时调试器会问你,是否要让调试器自动下载可执行程序没有的调试信息(至于从哪里下载,调试器是知道的,从上面截图中可以看出,是在一个网站上https://debuginfod.ubuntu.com)。这里我们只调试我们自己写的代码,因此没有必要下载其他的调试信息,因此选了不下载。
然后调试器会将程序暂停在main函数的第一据可执行代码上。在截图中,可以看出breakpoint 1,main()at debug_1.c:10。啥意思呢?就是第一个断点,在main函数中,main函数在debug_1.c这个源文件中,暂停在源文件的第10行。
同时可以看见,10 x=20;这一行,这一行表示被暂停的行号10和这行的代码 x=20;(注意,此时这行还没有被执行)。
然后这个时候,我们希望程序暂停在return 0;这一句源代码上,这样我们就可以查看y的值以及和y相关的其他信息。
为了达到上述目的,我们输入 b 15。
(return 0; 在第15行,b表示breakpoint断点的意思)
回车后出现断点设置成功的信息。然后我们输入c回车。(c表示continue的意思,就是继续执行的意思,因为我们在第15行设置了断点,那么程序继续执行,到第15行被暂停了下来,如上图,显示 breakpoint 2, main() at debug_1.c:15。同时显示第十五行的源代码 return 0;暂停在这里后,调试器等待我们输入下一个调试命令。
我们在这里暂停的目的是查看y的值,以及y这个变量所在内存中,二进制序列的情况。如何查看呢?
首先,我们用p y来查看y的值:
p表示print的意思,y是变量的名字。合起来就是打印y变量的值。可以用计算机算一下,0x12345678是不是就是305419896。如果不是,肯定程序哪里有问题了,如果是就表明没有问题。
现在我们还想看一下,y变量所在内存中,二进制序列的情况。
命令x的用法如下:
x/nfu 地址
n-内存单元个数
f:
x 16进制
d 10进制
u 10进制无符号
o 8进制
t 二进制
a 16进制
i 执行格式
c 字符格式
f 浮点数格式
u:
b 单字节
h 双字节
w 四字节
g 八字节
x/4xb &y的意思是,4-显示四个单元,x-以十六进制的格式显示,b每个单元一个字节。回车后,显示如下:
0x7fffffffe02c: 0x78 0x56 0x34 0x12
0x7fffffffe02c是y变量的内存地址,以十六进制的形式显示。0x78是内存地址为0x7fffffffe02c的一个字节的二进制串,只不过是以十六进制的形式显示,这里我们没有必要将其翻译成二进制形式。以此类推,0x7fffffffe02d地址的字节中,二进制串是0x56,0x7fffffffe02e地址的字节中,二进制串是0x34...。
通过观察,我们会发现y的值是0x12345678,而内存中从低地址到高地址的二进制串是:0x78 56 34 12。正好顺序反了。在高地址处,存放的是32位整数的高位数字,而在低地址上,存放的是32位整数的低位数字。还有一种存储方法,就是高地址处,存放0x78,而低地址处,存放0x12,这样就更符合我们人类的阅读习惯了。两种方法都可以,但总得选一种吧。此时有的人就选第一种,违反人类阅读习惯的方法,有的人就选第二种,符合人类阅读习惯的。我们现在用的最多的是第一种,叫做小端表示法,就是我们现在这个环境中使用的方法。
那现在如何结束调试呢?输入quit,或者q回车,然后再输入y就可以了。