拒绝!!迷信式调试
小目录
1. 小概念先看过来
1.1 debug?调试是个啥
调试(debugging / debug),又称除错,是发现和减少计算机程序或电子仪器设备中程序错误的一个过程。基本步骤如下:
- 发现程序错误的存在(程序员自己、测试人员、用户)
- 以隔离、消除等方式对错误进行定位 – 报bug(测试人员)
- 确定错误产生的原因
- 提出纠正错误的解决方法
- 对程序错误予以改正,重新测试
1.2 debug版本和release版本又是什么
debug – 调试版本:包含调试信息,不做任何优化,便于程序员调试程序。
release – 发布版本:进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好的使用。
注意:
- 相关版本信息可以在文件夹中点进去打开exe文件查看。
- release版本无法一步步进行调试,同一个代码release版本的exe程序会比debug版本大。
- 在Windows环境中调试准备,选择“debug”环境
2. Windows环境下的调试技巧
2.1 准确运用“快捷键”
Fn - 辅助功能键
- F5 – 启动程序,经常用来直接跳到(逻辑上的)下一个断点处
- ctrl+F5 – 开始执行(不调试)
- shift+F5 – 停止调试
- ctrl+shift+F5 – 重启调试
- F9 – 设置/取消断点(可在程序的任意位置),经常配合F5使用,可以使得程序在需要的位置随意停止执行,继而一步一步执行
- ctrl+F9 – 停止断点
- ctrl+shift+F9 – 删除全部断点
- 小tips:
条件断点(在循环内设置断点的位置):在断点上右击–>“条件”–>进行相应调整
- F10 – 逐过程,一次处理一个过程(一条语句、一次函数调用)
- F11 – 逐过程,一次处理一个语句,可以进入函数内部(最常用)
2.2 调试的时候查看程序当前信息
操作步骤:调试开始 ->“调试”->“窗口”->“自动窗口/局部变量/监视/内存/调用堆栈”->“ ”->“ ”
2.2.1 查看临时变量的值
在调试起来后,用于查看临时变量的值
操作步骤:调试开始 ->“调试”->“窗口”->“自动窗口/局部变量/监视/内存/调用堆栈”->…
监视:手动设置确定要观察的变量信息(只要是合法表达式都可以进行监视),不会随着程序执行的脱离而消失。更方便调试、观察。
监视函数中的连续数组(传址):在监视中输入 -> “数组名,要监视的元素个数”
自动窗口:自动显示程序执行过程中,出现的当前变量信息,供于观察。
局部变量:自动显示程序执行过程中,上下文环境的局部变量信息,供于观察。
2.2.2 查看内存信息
内存:通过输入地址/数组名来观察内存中真实存放的以16进制显示的数据。
2.2.3 查看调用堆栈:
调用堆栈:反馈了函数的调用逻辑。
(此处的栈是数据结构中的栈)
2.2.3 查看反汇编
调试起来后右击选项中也有反汇编选项。
2.2.3 查看寄存器
方法1:调试起来后在“窗口”中选择寄存器
方法2:调试起来后在“监视”窗口中输入寄存器的名字进行观察
3. !!!经典调试实例!!!
放出一段简单却诡异的代码,如下:
int main()
{
int i = 0;
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
for (i = 0; i <= 12; i++)
{
arr[i] = 0;
printf("hehe\n");
}
return 0;
}
肯定有同学很疑惑,这段代码要说有bug除了不一定被编译器查出的越界访问,他诡异在哪?下面是在vs2019中运行起来后的界面给大家看看:
没有被编译器查出越界访问的错误,但是程序进入了死循环…
那究竟是什么原因造成了这个隐形的bug呢?我们用调试来排查问题所在!
- 开始调试,打开监视,“名称”栏输入所有会出现的元素吧:
- 在不断的F10下,程序走到i=12都没有任何问题
- 直到程序走过了arr[i]=0
i的值原本是12,在这次arr[i]=0;的赋值后变成了0,导致i的值无法大于12,无法满足条件走不出循环。 - 眼尖的同学已经发现了,i和arr[12]在监视中的值是一样的,即使还没有调试到第12个循环arr[12]也跟着i一起变化。联系arr[12]赋值后改变i的值,我们在监视中添加i和arr[12]的地址来看看:
地址居然相同…这也解释了为何arr[i]与i的值始终一致了。 - 调试–找出bug原因这一步,便完成了。
这其实是一道笔试题,也在《C陷阱和缺陷》一书中写出来了,具体的原因,笔者用模仿笔试答题的方法为同学们解答:
- 栈区内存的使用习惯是:先使用高地址处的空间,再使用低地址处的空间
- 数组随着下标的增长地址是由低到高变化的
- 如果i和arr之间有适当的空间,利用数组的越界操作就可能会覆盖到i,就可能导致死循环的出现
- 此处间隔两个空间和环境和编译器有关
4. 总结
想要写出优秀的代码,必然要有很好的代码逻辑,可调试性和可维护性,常见的coding技巧如下:
- 使用assert
- 尽量使用const
- 养成良好的代码风格
- 添加必要的注释
- 避免编码的陷阱