【C语言】VS调试常用技巧

一、什么是bug

        bug中文意思是“昆虫”,表示在电脑系统或程序中没有被发现的问题,也叫安全漏洞

        bug这个名字起源于一个小故事:很久很久以前,有一个美国的电脑专家,负责处理船上的电脑出现的各种问题。有一次发现电脑(那个时候的电脑都是很大很大的)不能正常工作了,最后检查出,是一只小飞蛾卡在了电脑一个零件上,被高压电死了。后来这个电脑专家把这件事记在了报告上,并把小飞蛾尸体粘在了上面,用"bug"称呼这个电脑问题,从此沿用至今。

二、什么是debug

        debug形象的翻译就是消除bug,中文称调试。首先我们承认出现了问题后,就需要通过调试技巧,找到问题,然后修复代码,重新测试。

三、Debug和Release

        在VS上可以看到有debug和release的区分:

       这是用来设置编译器编译后产生哪种版本的可执行程序。

        Debug被称为调试版本,包含很多调试信息,没有对程序进行任何优化,是便于开发人员调试的。

        Release被称为发布版本,没有调试信息,对程序的大小和速度都进行了优化,不能进行调试,是给用户用的。因为测试人员是站在用户的角度进行测试的,并且Release版本会对程序进行优化,如果测试人员使用优化前的Debug版本进行测试,它的使用效果跟用户的使用效果是不一样的。所以开发人员写好代码后,交给测试人员的也是Release版本。

        因为Release版本相较Debug版本没有调试信息,并且对程序大小进行了优化,所以Release版本要小很多。

四、VS的调试快捷键

        环境准备:VS中改为Debug模式。

        常用的几个快捷键:

(1)F9

        用于创建断点和取消断点。把光标点到想要打断点的那一行,按F5(因为快捷键跟我的电脑自带的快捷键冲突了,要用Fn + F5,后面的快捷键也是一样的)。直接鼠标点击最左边灰色的部分也可以打断点。

        断点:为什么要有断点?比如上图所示,我想调试第二个for循环,第一个for循环的循环次数又很多,一步步调试岂不是很浪费时间。打了断点,就可以直接跳到打断点的那一行停止,再用F10、F11进行细节上的调试。F9一般和F5搭配使用。

        条件断点:在断点上右击,条件,出现下面的界面,设置条件,按回车保存。条件满足断点才生效。

        按F5跳到条件断点位置,但发现并没有在断点处停下,而是执行完了整个程序:

        原因是条件断点不支持把断点打在循环语句上,应该打在循环里面

        这次在条件断点处停了下来。

(2)F5

        启动调试,跳到下一个断点处。常与F9搭配使用。

(3)F10

        逐过程,过程可以是一条语句,或者函数调用,不会进入到调用的函数内部

(4)F11

        逐语句,每按一次执行一条语句,会进入函数内部,比F10更观察细节。

(5)Ctrl + F5

        开始执行不调试。直接运行程序,而不调试程序。

        上面就是常用的VS快捷键。VS的更多快捷键,请参考:

        VS中常用的快捷键_vs快捷键-CSDN博客

五、监视

        如果想观察上下文环境中变量值的变化,可以使用监视窗口:

        进入调试环境中 >> 调试 >> 窗口 >> 监视 >> 选择监视窗口(随便哪个都行)。

        因为input函数传过去的是数组地址,所以添加监视项arr后只能看到第一个元素值的变化。想要看到所以元素值,可以加上长度10:

六、内存

        如果觉得观察变量值的变化还不够,还想观察变量在内存中的变化,可以使用VS中的内存功能:进入调试环境 >> 调试 >> 窗口 >> 内存 >> 选择内存(随便哪个都行)

        输入想要观察的变量地址,数组的地址就是数组名arr,变量ch就是&ch。

        这里可以调整显示的内存里面十六进制数据的列数,十六进制两个数就代表一个字节(一列)。因为整型为4个字节,就调整为4列,好观察:

        内存里面的数据实际上是以二进制存储的,但这里是为了方便展示,展示的是十六进制的。右边很多问号那一块很少的参考意义,就是把内存里的值尝试解读了一下。

        上面就是常用的调试窗口。调试的窗口里边还有很多其他窗口:自动窗口(调试的时候,会自动监视编译器觉得当前应该监视的值)、局部变量、反汇编、寄存器等,可以自己试一下。

七、调试举例

        在VS2022、X86、Debug环境下,编译器不做任何优化(在默认情况下),下面的代码执行结果是?

#include <stdio.h>
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;
}

        可以明显看出,循环访问数组元素时越界了。运行结果如下:

        进入了死循环,出乎意料的结果。我们动手调试一下,监视窗口:

        i 变为12,下一步是给arr[12]赋值为0:

        会发现arr[12]赋值为0过后,i 的值也意外的跟着变为了0。所以接下来就是又从arr[0]开始赋值到 i 变为 12,又给arr[12]赋值为0后,i又变回了0,从而进入死循环。那么为什么 i 会跟着arr[12]的变化而变化呢?查看一下内存窗口:

        输入arr[12]和 i 会发现是一个地址,所以 i 会随着arr[12]的变化而变化。什么原因呢?有两点:

  • 在栈区中,变量的存放【默认下】是从高地址向低地址存放的。所以 i 的地址比 arr 大。
  • 数组在内存中存放时,地址是随着下标增长而增大的。

        那为什么数组 arr 和 i 之间刚好间隔两个整型大小呢?纯属巧合,内存分配和地址分配是编译器指定的。所以这道题的题目,要注明环境。比如,变量存放是先放高地址,再放低地址这个规则,在VS_X64环境中是相反的:

        在Release版本(没有调试信息,不能调试)的程序中,同样的代码运行时不会发生死循环的问题。(前面说过,Release会对程序进行优化,可能也是考虑到越界问题,防止越界把变量i修改了,定义时,不论 i 放在arr前面还是后面,在内存中存储时,都是把 i 放在相对arr低的地址):

        调试能增强程序员对程序本质的理解。调试时,要心中有数,清楚希望代码怎么执行,再看有没有按照我们的期望执行。

八、常见的错误分类

(1)编译错误

        在之前学过,源程序先要编译成目标文件,编译错误是在编译时发生的错误,一般是语法错误。如下:

        把鼠标在语法错误上点一下,就能定位到对应的错误的代码行。

(2)链接错误

        在链接阶段发生的错误就叫链接错误。链接阶段就是把所有目标文件与库文件链接起来生成可执行文件的过程。链接错误前都有“LNK”字样,一般是没有写调用的库函数对应的头文件、调用的函数名写错了、库函数不存在等错误,如下例子,调用了printf函数,但没有写<stdio.h>头文件:

        点击链接错误,是不能定位代码错误在哪一行的。

(3)运行错误

        运行错误是在程序运行阶段发生的错误,需要调试解决。

  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值