Visual Studio实用调试技巧---让你成为高端的程序员

简介: Visual Studio实用调试技巧---让你成为高端的程序员

🍁博客主页:江池俊的博客

💡代码仓库:江池俊的代码仓库

🎪我的社区:GeekHub

🍁 如果觉得博主的文章还不错的话,请点赞👍收藏🌟 三连支持一下博主💞


一、 什么是bug?



在计算机编程领域,bug指的是程序中存在的错误或缺陷。当程序无法按照预期的方式运行,或者产生意料之外的结果时,通常会被认为是有bug。bug可以导致程序崩溃、产生错误的输出、不正确的行为或不一致性。bug可以是由代码错误、逻辑错误、算法问题、输入错误、外部环境因素等引起的。发现和修复bug是软件开发中的重要环节,通常需要进行调试和测试来定位和解决问题。b33ced8284544000955807465d79e0b2.png

第一次被发现的导致计算机错误的飞蛾,也是第一个计算机程序错误。

参考链接


二、调试是什么?有多重要?



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


🍁 调试的基本步骤


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


三、Debug和Release版本的介绍。



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

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


代码:

#include <stdio.h>
int main()
{
 char *p = "hello world.";
 printf("%s\n", p);
 return 0;
}


上述代码在Debug环境的结果展示:

d70f7fa053eb4b6b86be8828bf17ca2a.png

上述代码在Release环境的结果展示:

641931e781d547648d389dfcad36db7c.png


Debug和Release反汇编展示对比:


fec8ce6ac6b540639c0eb003337ee6ab.png


所以我们说调试就是在Debug版本的环境中,找代码中潜伏的问题的一个过程。


四、Windows环境调试介绍



  • Debug为调试版本,一般在开发完成后发布工程前,调试代码都是在Debug模式下进行的。
  • Release版本是不能调试的,一般都是在Debug版本下调试的,Release版本一般编译器会进行大量的优化,删除无用的代码,指令的次序调整等,让其速度更快。

86139047131a403fa9cbc4820caedb70.png

在环境中选择 debug 选项,才能使代码正常调试。


🍁常用快捷键


  • F5 --> 启动调试,经常用来直接跳到下一个断点处。一般不会单独使用,而是配合F9一起使用。
  • F9 --> 创建断点和取消断点。断点的重要作用:可以在程序的任意位置设置断点。这样就可以使得程序在想要的位置随意停止执行,继而一步步执行下去。
    F10 --> 逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句。(遇到函数直接执行完整个函数,不进入函数内部)
    F11 --> 逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部(这是最 常用的)
  • CTRL + F5 --> 开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用。


想知道更多快捷键?点我


🍁调试的时候查看程序当前信息


1. 查看临时变量的值:在调试开始之后,用于观察变量的值。

监视窗口:


43e73489644b46be83a730e1b9a1685e.png


2. 查看内存信息:在调试开始之后,用于观察内存信息。

内存窗口:

33be1dca72f44760a7e459dcddb0e426.png


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

调用堆栈窗口:

image.png


4. 查看汇编信息:在调试开始之后,有两种方式转到汇编:

(1)第一种方式:右击鼠标,选择【转到反汇编】:

image.png


(2)第二种方式:调试窗口找到反汇编

3b689fe3b5a648e383920ae444f1ebbf.png

5. 查看寄存器信息:可以查看当前运行环境的寄存器的使用信息。


3998eb1228f246f1bd9939034c8fea77.png


多多动手,尝试调试,才能有进步。

一定要熟练掌握调试技巧, 初学者可能80%的时间在写代码,20%的时间在调试。但是一个程序员可能20%的时间在写 程序,但是80%的时间在调试。


五、调试实例



🍁 实例1


实现代码:求 1!+2!+3! …+ n! ;不考虑溢出。

d55bfe4abc974c378300b47797b4dbc0.png

代码改正:

#include<stdio.h>
int main()
{
  int i = 0;
  int sum = 0;//保存最终结果
  int n = 0;
  scanf("%d", &n);
  for (i = 1; i <= n; i++)
  {
    int ret = 1;//保存n的阶乘
    int j = 0;
    for (j = 1; j <= i; j++)
    {
      ret *= j;
    }
    sum += ret;
  }
  printf("%d\n", sum);
  return 0;
}


🍁 实例2


af8d96bcf27d4931842f7f797e389b3a.png


注意:此代码非常依赖环境,在vs2022 debug版本x86环境下才是死循环的,而release版本下此代码会被优化,改变内存布局,不会死循环。

04d83c7588b1498e86cf714eb0e39e3d.png



六、如何写出好(易于调试)的代码



优秀代码的特点:

  1. 代码运行正常
  2. bug很少
  3. 效率高
  4. 可读性高
  5. 可维护性高
  6. 注释清晰
  7. 文档齐全


常见的coding技巧:

  1. 使用assert
  2. 尽量使用const
  3. 养成良好的编码风格
  4. 添加必要的注释
  5. 避免编码的陷阱。


🍁assert的使用


assert 函数是一种在代码中用于测试和调试的工具,它用于在运行时检查一个条件是否为真。如果条件为真,则 assert 什么也不做,程序继续执行。但如果条件为假,assert 会引发一个异常(通常是 AssertionError),中断程序的执行。


示例:

模拟实现库函数:strcpy

/*
库函数strcpy
1.描述
C 库函数 char *strcpy(char *dest, const char *src) 把 src 所指向的字符串复制到 dest。
需要注意的是如果目标数组 dest 不够大,而源字符串的长度又太长,可能会造成缓冲溢出的情况。
2.声明
下面是 strcpy() 函数的声明。
char *strcpy(char *dest, const char *src)
3.参数
dest -- 指向用于存储复制内容的目标数组。
src -- 要复制的字符串。
4.返回值
该函数返回一个指向最终的目标字符串 dest 的指针。           */
char *my_strcpy(char * dst, const char * src)
{
        char * cp = dst;
        assert(dst && src);//断言,防止传进来的是空指针或野指针
        while( *cp++ = *src++ )
        { 
             ;
        }     /* Copy src over dst */
        return dst;
}


🍁空指针和野指针的危害


1.空指针(Null Pointer):

空指针是指不指向任何有效内存位置的指针,通常用空值(NULL)表示。空指针通常表示指针尚未初始化或不引用任何有效的内存。访问空指针通常会导致程序崩溃或未定义的行为。主要危害有:


  • 程序崩溃:访问空指针可能会导致程序直接崩溃,因为操作系统会捕获到这种无效的内存访问并终止程序。
  • 未定义行为:C语言标准规定对空指针的解引用是未定义行为,这意味着不同的编译器和平台可能会表现出不同的行为,包括奇怪的运行时行为和数据损坏。
  • 安全问题:攻击者可以利用空指针漏洞来执行恶意代码,从而造成系统的安全问题。


2.野指针(Dangling Pointer):

野指针是指在指针指向的内存位置被释放或无效后,仍然保持了该指针的值。在访问野指针时,可能会读取到无效的数据或者修改其他内存区域,导致未定义的行为。主要危害有:

  • 数据损坏: 野指针可能会导致数据损坏,因为程序可能会误用已经释放或者不再有效的内存位置。
  • 难以调试: 由于野指针可能导致未定义行为,程序可能会表现出奇怪的错误,这会使调试变得非常困难。


🍁const的作用


const修饰指针变量的时候:

1. const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本身的内容可变。

#include <stdio.h>
int main() {
    int num = 5;
    const int* ptr = &num;  // const在*的左边,指针本身可以变,但是指向的内容不可变
    // *ptr = 10;  // 这里会产生编译错误,因为不能通过ptr修改num的值
    num = 10;  // 可以通过num直接修改值
    printf("num: %d\n", num);  // 输出:num: 10
    int another_num = 20;
    ptr = &another_num;  // 可以将ptr指向另一个整数
    return 0;
}

在上述示例中,ptr 是一个指向 const int 的指针,这意味着不能通过 ptr 来修改它指向的内容,但可以通过修改 num 的值来间接地修改指针所指向的内容。另外,可以改变 ptr 指向其他整数。


2. const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

#include <stdio.h>
int main() {
    int num = 5;
    int* const ptr = &num;  // const在*的右边,指针本身不可变,但是指向的内容可以改变
    *ptr = 10;  // 可以通过ptr修改num的值
    printf("num: %d\n", num);  // 输出:num: 10
    // 以下操作是不允许的,因为ptr已经被声明为const,不能指向其他内存
    //int another_num = 20;
    // ptr = &another_num;  // 编译错误
    return 0;
}


在上述示例中,ptr 是一个指向 num 的常量指针,这意味着不能通过 ptr 来改变它指向的位置,但可以通过 *ptr 来修改它指向的内容。


练习:

模拟实现一个strlen函数

#include <stdio.h>
int my_strlen(const char *str)
{
    int count = 0;
    assert(str != NULL);//断言,也可以写成 assert(str);
    while(*str)//判断字符串是否结束
   {
        count++;
        str++;
   }
    return count;
}
int main()
{
    const char* p = "abcdef";
    //测试
    int len = my_strlen(p);
    printf("len = %d\n", len);
    return 0;
}


七、编程常见的错误



  1. 编译型错误
  • 产生原因:编译型错误是在编译阶段发生的错误,通常是由于语法错误、类型错误、未定义的标识符等造成的。编译器无法正确解析代码,因此无法生成可执行文件。
  • 解决方法:仔细检查代码,确保语法正确、类型匹配,并确保使用的标识符在正确的作用域中定义。查看编译器的错误信息和警告,逐一修复问题。相对来说简单。


  1. 链接型错误
  • 产生原因:链接型错误发生在链接阶段,当编译器尝试将多个源文件组合成一个可执行文件时。常见的链接错误包括重复定义、未定义的符号等。
    解决方法:确保不同源文件中的函数和变量只有一次定义,避免重复定义。如果遇到未定义的符号错误,检查是否缺少某个库文件的链接,或者确保函数定义在正确的源文件中。一般是标识符名不存在或者拼写错误。


  1. 运行时错误
  • 产生原因:运行时错误发生在程序执行阶段,可能由于无效的内存访问、除以零、类型不匹配等引起。这些错误可能导致程序崩溃、产生未定义行为或者不正确的结果。
    解决方法:使用合理的错误处理机制来捕获和处理运行时错误。例如,对于可能导致除以零的情况,可以在执行之前进行条件检查。使用异常处理机制(例如C++ 中的 try-catch)来捕获异常情况并进行适当的处理。确保指针的正确初始化和检查,以避免空指针或野指针问题。要习惯借助调试,逐步定位问题。最难搞。


🔥今天的分享就到这里,如果觉得博主的文章还不错的话,请👍三连支持一下博主哦🤞

af6170a794b24656946e6577906afada.gif


目录
相关文章
|
9月前
|
存储 安全 NoSQL
Visual Studio 2022 你必须知道的实用调试技巧
大家好,前面我们讲了Visual Studio 2022的安装和使用以及scanf报错的解决方法 今天分享一些实用的 VS 技巧,而这些技巧很多人都不知道。当你以后在工作中遇到:我在同事电脑上解决问题,或在会议上演示代码示例时,使用了一些 VS “骚”操作,他们会好奇地问:你是怎么做到的?有时候你知道一些别人不知道的小技巧便可以让你显得更专业。 PS:本文所有示例演示均基于 Visual Studio 2022 版本。
193 0
|
9月前
|
NoSQL 编译器 程序员
Visual Studio2022编译器实用调试技巧
Visual Studio2022编译器实用调试技巧
|
11月前
|
C语言 C++
【五一创作】Visual Studio常用调试技巧的温习
【五一创作】Visual Studio常用调试技巧的温习
75 0
|
12月前
|
安全 程序员 编译器
visual studio 2022调试技巧介绍(2)
1.什么是bug? bug是计算机领域专业术语,bug原意是“臭虫”,现在用来指代计算机上存在的漏洞,原因是系统安全策略上存在的缺陷,有攻击者能够在未授权的情况下访问的危害。 漏洞是在硬件、软件、协议的具体实现或系统安全策略上存在的缺陷,从而可以使攻击者能够在未授权的情况下访问或破坏系统。 2.调试是什么,如何调试? 2.1 调试的概念 调试简单来说就是调试是保证所提供的设备能够正常运行的必须程序。 通过一些工具对代码进行检测,修复bug。
112 0
visual studio 2022调试技巧介绍(2)
|
12月前
|
安全 程序员 编译器
visual studio 2022调试技巧介绍(1)
1.什么是bug? bug是计算机领域专业术语,bug原意是“臭虫”,现在用来指代计算机上存在的漏洞,原因是系统安全策略上存在的缺陷,有攻击者能够在未授权的情况下访问的危害。 漏洞是在硬件、软件、协议的具体实现或系统安全策略上存在的缺陷,从而可以使攻击者能够在未授权的情况下访问或破坏系统。 2.调试是什么,如何调试? 2.1 调试的概念 调试简单来说就是调试是保证所提供的设备能够正常运行的必须程序。 通过一些工具对代码进行检测,修复bug。
264 0
|
编译器 程序员 C语言
你是真的“C”——Visual Studio 2022(VS2022)编译器 -—实用调试技巧(下)
4.多多动手,尝试调试,才能有进步。🙌 5. 一道经典面试题目,这里用调试进行演示分析:🙌 6. 如何写出好(易于调试)的代码。🙌 6.1 优秀的代码: 6.2 典型栗子示范: 6.3 const的作用 7. 编程常见的错误🙌 7.1 编译型错误 7.2 链接型错误 7.3 运行时错误 总结撒花💞
199 0
|
算法 程序员 编译器
你是真的“C”——Visual Studio 2022(VS2022)编译器 -—实用调试技巧(上)
前言🙌 1. 什么是bug?🙌 2. 调试是什么?有多重要?🙌 2.1 调试是什么? 2.2 调试的基本步骤 2.3 Debug和Release的介绍。 3. Windows环境调试介绍🙌 3.1 调试环境的准备 3.2 学会快捷键 3.3 调试的时候查看程序当前信息 3.3.1 查看临时变量的值 3.3.2 查看内存信息 3.3.3 查看调用堆栈 3.3.4 查看汇编信息 3.3.5 查看寄存器信息
416 0
|
IDE 安全 JavaScript
Visual Studio调试技巧 -- Attach to Process
一般写完代码时,我们通常会启动调试运行一下看看是否正确,启动运行的方式无非是F5-- Start Debugging 或 Ctrl+F5-- Start Withour Debugging(注:不同版本或系统环境不同时,快捷键或有所变化),如下图1。不用说大家都能感觉到,使用Ctrl+F5调试时程序从启动到运行通常比使用F5快得多。使用Ctrl+F5时通常是想快速运行一遍,以便检查程序运行的结果是否符合预期。使用F5时通常是想查看代码内部的运行情况,以便检查到底是哪一步出了问题,或者所有参数是否都正确。
Visual Studio调试技巧 -- Attach to Process
|
C++
Visual Studio 原生开发的10个调试技巧(二)
原文:Visual Studio 原生开发的10个调试技巧(二)   我以前关于 Visual Studio 调试技巧的文章引起了大家很大的兴趣,以至于我决定分享更多调试的知识。以下的列表中你可以看到写原生开发的调试技巧(接着以前的文章来编号)。
1159 0