VS调试技巧 + 经典调试案例

拒绝!!迷信式调试

小目录

  • 1. 小概念先看过来
    • 1.1 debug?调试是个啥
    • 1.2 debug版本和release版本又是什么
  • 2. Windows环境下的调试技巧
    • 2.1 准确运用“快捷键”
    • 2.2 调试的时候查看程序当前信息
      • 2.2.1 查看临时变量的值
      • 2.2.2 查看内存信息
      • 2.2.3 查看调用堆栈:
      • 2.2.3 查看反汇编
      • 2.2.3 查看寄存器
  • 3. !!!经典调试实例!!!
  • 4. 总结


1. 小概念先看过来

1.1 debug?调试是个啥

调试(debugging / debug),又称除错,是发现和减少计算机程序或电子仪器设备中程序错误的一个过程。基本步骤如下:

  1. 发现程序错误的存在(程序员自己、测试人员、用户)
  2. 以隔离、消除等方式对错误进行定位 – 报bug(测试人员)
  3. 确定错误产生的原因
  4. 提出纠正错误的解决方法
  5. 对程序错误予以改正,重新测试

1.2 debug版本和release版本又是什么

debug – 调试版本:包含调试信息,不做任何优化,便于程序员调试程序。

release – 发布版本:进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好的使用。

注意:

  1. 相关版本信息可以在文件夹中点进去打开exe文件查看。
  2. release版本无法一步步进行调试,同一个代码release版本的exe程序会比debug版本大。
  3. 在Windows环境中调试准备,选择“debug”环境VS调试技巧 + 经典调试案例_第1张图片

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中运行起来后的界面给大家看看:
VS调试技巧 + 经典调试案例_第2张图片
没有被编译器查出越界访问的错误,但是程序进入了死循环…
那究竟是什么原因造成了这个隐形的bug呢?我们用调试来排查问题所在!

  • 开始调试,打开监视,“名称”栏输入所有会出现的元素吧:
    VS调试技巧 + 经典调试案例_第3张图片
  • 在不断的F10下,程序走到i=12都没有任何问题
    VS调试技巧 + 经典调试案例_第4张图片
    VS调试技巧 + 经典调试案例_第5张图片
  • 直到程序走过了arr[i]=0
    VS调试技巧 + 经典调试案例_第6张图片
    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陷阱和缺陷》一书中写出来了,具体的原因,笔者用模仿笔试答题的方法为同学们解答:

  1. 栈区内存的使用习惯是:先使用高地址处的空间,再使用低地址处的空间
  2. 数组随着下标的增长地址是由低到高变化的
  3. 如果i和arr之间有适当的空间,利用数组的越界操作就可能会覆盖到i,就可能导致死循环的出现
  4. 此处间隔两个空间和环境和编译器有关
    VS调试技巧 + 经典调试案例_第7张图片

4. 总结

想要写出优秀的代码,必然要有很好的代码逻辑,可调试性和可维护性,常见的coding技巧如下:

  • 使用assert
  • 尽量使用const
  • 养成良好的代码风格
  • 添加必要的注释
  • 避免编码的陷阱

你可能感兴趣的:(C语言,c#)