【实用调试技巧】总是找不到Bug?手把手教你在vs2022中调试程序

举报
修修修也 发表于 2024/04/27 15:27:47 2024/04/27
【摘要】 什么是Bug?先来看看百度翻译对Bug的解释:​ 而这个让无数程序员感到每天都被噩梦支配的恐惧也有一段有趣的过往:1947年9月9日:第一个"Bug"被发现“1947 年 9 月 9 日,我们晚上调试机器的时候,开着的窗户没有纱窗,机器闪烁的亮光几乎吸引来了世界上所有的虫子。果然机器故障了,我们发现了一只被继电器拍死的飞蛾,翅膀大约 4 英寸。”格蕾丝·霍普(Grace Hopper)用发夹...

什么是Bug?

先来看看百度翻译对Bug的解释:

 而这个让无数程序员感到每天都被噩梦支配的恐惧也有一段有趣的过往:

1947年9月9日:第一个"Bug"被发现

“1947 年 9 月 9 日,我们晚上调试机器的时候,开着的窗户没有纱窗,机器闪烁的亮光几乎吸引来了世界上所有的虫子。果然机器故障了,我们发现了一只被继电器拍死的飞蛾,翅膀大约 4 英寸。”

格蕾丝·霍普(Grace Hopper)用发夹取出飞蛾,把它粘在日志里,并标注:“First actual case of bug being found”(找到了第一个 Bug)。这件计算机史上的奇闻轶事,使“Bug”作为计算机领域的专用词汇,一直沿用至今。

​ 历史上第一个bug图像


什么是调试?

既然有Bug的存在,那么作为程序员肯定要找Bug并且改Bug.

我们把找Bug的过程叫做调试.

我们在写代码的时候一定多思考,不然很容易像这样:

不会调试的程序员排查Bug现状:

 一个高质量的程序员一定要拒绝迷信式改Bug!


调试是什么?

调试(Debugging / Debug):又称除错,是发现和减少计算机程序或电子仪器设备中程序错误的一个过程.


调试的基本步骤

• 发现程序错误的存在

• 以隔离,消除等方式对错误进行定位

• 确定错误产生的原因

• 提出纠正错误的解决办法

• 对程序错误予以改正,重新测试


Debug和Relese的区别

Debug 通常称为调试版本,它包含调试信息,并且不作任何优化便于程序员调试程序


Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的以便用户很好地使用.

1.调试的区别

在Debug环境下,按F10(有fn键的话,需要按住fn+F10),启动调试,就可以使用如下功能了:

而在Release环境下,调试不好用,虽然也可以显示调试界面,但是完成不了像debug环境下的操作。


2.文件大小的区别

Debug环境生成的文件,里面因为包含了调试信息,所以占据的空间较大

Release环境下生成的文件,是提供给用户使用不需要用户调试,所以文件所占空间较小


3.反汇编的区别

Debug:较多

 Release:较少


4.代码执行结果不同

如下代码:

int main()

{

    int i = 0;

    int arr[10] = { 0 };

    for (i = 0; i <= 12; i++)

    {

        arr[i] = 0;

        printf("hello\n");

    }

    return 0;

}

Debug环境下,结果是死循环:

但在Release环境下却输出了13个"hello":

造成该结果的原因主要是Release环境会优化代码,使程序列在代码大小和运行速度上达到最优,以便用户能够很好地使用它。


windows环境下如何进行调试

1.调试环境的准备

首先我们要在编译器中选择Debug选项,才可以进行正常调试:


2.常用调试快捷键

vs2022中找到调试按钮,鼠标放上去就可以看到可以进行的操作及其快捷键.

常用快捷键有:

F5

启动调试,经常用来直接跳到下一个断点处.


F9

创建断点和取消断点
断点的重要作用,可以在程序的任意位置设置断点。
这样就可以使得程序在想要的位置随意停止执行,继而一步步执行下去.


F10

逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句。


 

F11

逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部(这是最常用的)


 

Ctrl+F5

开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用

想知道更多快捷键?VS中常用的快捷键大全


3.调试时查看程序相关信息

📌查看临时变量的值

🎏查看单一变量

当我们需要观察临时变量的值时,可以先按下F10/F11进入调试,然后在调试中选择:调试-窗口-监视-监视1,就可以打开监视窗口了.

然后在监视窗口输入想观察的变量,敲下回车就可以观察这个变量了.


🎏查看数组成员

当我们需要观察数组中的成员时,可以输入"数组名,大小",然后点击旁边的小三角可以一次性查看全部的数组成员了.


🎏查看结构体成员

当我们需要观察结构体中的成员时,可以输入结构体名,然后点击旁边的小三角可以一次性查看全部的结构体成员了.


 📌查看内存信息

调试开始之后(按下F10/F11)后,用于观察内存信息.

内存窗口如下:

 如果不习惯看16列的数的话,也可以选择将它改为4列:

观察内存可以直接输入地址观察,也可以写取地址表达式观察:

这样输入它然后敲回车就可以直接带你找到该变量在内存中的位置:


📌查看调用堆栈

通过调用堆栈,可以清晰的反应函数的调用关系以及当前调用所处的位置.


📌查看汇编信息

在调试开始之后,有两种方式转到汇编.

🎏第一种方式

在空白区右击鼠标,选择"转到反汇编":

然后就可以查看汇编信息了:

🎏第二种方式

在调试开始后,点击"调试-窗口-反汇编"即可查看汇编信息.

效果如下:


📌查看寄存器

进入调试后,点击"调试-窗口-寄存器"即可查看当前运行环境的寄存器的使用信息.


一些练习的例子

师傅领进门,修行靠个人.下面有几个可以调试的例子,大家可以拷贝进自己的编辑器边调试边分析这些代码的问题到底出在哪里.

实例一:实现代码:求1!+2!+3!+...n!

int main()

{

    int i = 0;

    int sum = 0;//保存最终结果

    int n = 0;

    int ret = 1;//保存n的阶乘

    scanf("%d", &n);

    for (i = 1; i <= n; i++)

    {

        int j = 0;

        for (j = 1; j <= i; j++)

        {

            ret *= j;

        }

        sum += ret;

    }

    printf("%d\n", sum);

    return 0;

}

不考虑溢出的情况下,这时候我们如果输入3,期待输出9,但实际输出的是15.

在调试中我希望大家可以搞清楚:为什么会这样?哪一步出了问题?如何解决?


 实例二

#include <stdio.h>

int main()

{

    int i = 0;

    int arr[10] = { 0 };

    for (i = 0; i <= 12; i++)

    {

        arr[i] = 0;

        printf("hello\n");

    }

    return 0;

}

 这个例子我希望大家可以结合查看内存信息以及查看变量的方法来找出让程序陷入死循环的原因.


如何写好易于调试的代码

优秀的代码:

1. 代码运行正常

2. Bug很少

3. 效率高

4. 可读性高

5. 可维护性高

6. 注释清晰

7. 文档齐全

常见的Coding技巧:

1. 使用assert (如果有不太清楚assert函数用法的可以移步这篇博客:不想改bug?程序员必须学会使用的报错函数assert!(断言函数详解) )

2. 尽量使用const

3. 养成良好的编码风格

4. 添加必要的注释

5. 避免编码的陷阱

 多看和学习大佬是如何编写优秀的代码的,如下面是一个模拟实现库函数strcpy的代码:

/***

*char *strcpy(dst, src) - copy one string over another

*

*Purpose:

* Copies the string src into the spot specified by

* dest; assumes enough room.

*

*Entry:

* char * dst - string over which "src" is to be copied

* const char * src - string to be copied over "dst"

*

*Exit:

* The address of "dst"

*

*Exceptions:

*******************************************************************************/

char* strcpy(char* dst, const char* src)

{

    char* cp = dst;

    assert(dst && src);

    while (*cp++ = *src++)

        ; /* Copy src over dst */

    return(dst);

}

需要注意学习的点:

1. 分析参数的设计(命名,类型),返回值类型的设计

2. assert的使用.(如果有不太清楚assert函数用法的可以移步这篇博客:不想改bug?程序员必须学会使用的报错函数assert!(断言函数详解) )

3. 野指针,空指针

4. const的使用

5. 注释的添加


编程常见的错误

📌编译型错误

直接看错误提示信息(双击),解决问题。或者凭借经验就可以搞定。相对来说简单。

📌链接型错误

看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。

一般是标识符名不存在或者拼写错误。

📌运行时错误

借助调试,逐步定位问题。


结语

没有什么技能是天生就会的,都是通过后天的不断学习和练习最终掌握的,希望大家可以做一个有心人,积累排错经验。也祝大家"0 Error 0 Warning",Bug全Free!希望这篇博客能对大家有所帮助,一起学习,一起进步!

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。