VS实用调试技巧

一.什么是BUG🐛
Bug一词的原意是虫子,而在电脑系统或程序中隐藏着的一些未被发现的缺陷或问题,人们也叫它"bug"。这是为什么呢?这就要追溯到一个程序员与飞蛾的故事了。
Bug的创始人格蕾丝·赫柏(Grace Murray Hopper),是一位为美国海军工作的电脑专家,也是最早将人类语言融入到电脑程序的人之一。而代表电脑程序出错的“bug” 这名字,正是由赫柏所取的。1947年9月9日,赫柏对Harvard Mark II设置好17000个继电器进行编程后,技术人员正在进行整机运行时,它突然停止了工作。于是他们爬上去找原因,发现这台巨大的计算机内部一组继电器的触点之间有一只飞蛾,这显然是由于飞蛾受光和热的吸引,飞到了触点上,然后被高电压击死。所以在报告中,赫柏用胶条贴上飞蛾,并把“bug”来表示“一个在电脑程序里的错误”,“Bug”这个说法一直沿用到今天。

格蕾丝·赫柏的报告
二.调试及其重要性📝
2.1 什么是调试
所有发生的事情都一定有迹可循,如果问心无愧,就不需要掩盖也就没有迹象了,如果问心有愧,就必然需要掩盖,那就一定会有迹象,迹象越多就越容易顺藤而上,这就是推理的途径。顺着这条途径顺流而下就是犯罪,逆流而上,就是真相。
而我们程序员就好比一个侦探,一个用来寻找bug,修改bug的侦探。人们将这个过程叫做"Debug"(中文称作"调试"),意即"捉虫子"或"杀虫子"。每一次调试都是尝试破案的过程。
调试(英语:Debugging / Debug),又称除错,是发现和减少计算机程序或电子仪器设备中程序错误的一个过程。
2.2 调试的基本步骤
发现程序错误的存在
以隔离、消除等方式对错误进行定位
确定错误产生的原因
提出纠正错误的解决办法
对程序错误予以改正,重新测试
本文将详细介绍在windows系统的VS环境下的调试过程。
2.3 Debug与Release的介绍
在VS中,我们会发现我们的程序可以在两个环境下运行,这两个环境就是Debug版本和Release版本,它们二者有何区别呢?

VS中的Debug与Release
Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。
Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
我们可以分别在两种环境下编译代码生成对应的可执行程序如下:

显然,Release版本的可执行文件比Debug版本的小很多,说明编译器对其作了优化。
因此,我们日常所说的调试是在Debug版本的环境下进行的,这是因为Release版本无法进行调试,其调试信息被编译器优化了。而对于测试人员来说,由于要站在用户的角度上来测试程序是否能正常使用,因此测试是在Release版本的环境下进行的。
那么,Release版本下编译器可能会做什么优化呢?请看如下代码:
#include <stdio.h>int main()
{int i = 0;int arr[10] = {0};for(i=0; i<=12; i++){arr[i] = 0;printf("hehe\n");}return 0;
}
我们很容易就发现对数组进行了越界访问,当程序运行起来时应该会崩溃。但是在Debug环境下我们发现程序并没有崩溃而是陷入了死循环:

我们运行调试代码转到反汇编如下:

我们知道,数据在栈上的开辟是从高地址向低地址处开辟的,因此在Debug环境下变量i的地址比数组arr的地址高。而在数组内部数据的存储是从低地址向高地址的,因此首元素地址arr是在数组所在空间的低位,如下:

我们很惊讶的发现(arr+12)就是变量i的地址。当循环过程中i等于12时,此时将arr[i]改为0就等价于将i的值修改为0,然后i++后i等于1小于12,继续进行循环,依次反复形成了死循环。如下:

整个过程简化图如下:

而在Release版本的环境下程序并不会出现死循环的问题:

我们可以打印出此时变量i和数组在栈上的地址如下:

我们发现此时变量i被编译器优化到低地址处,arr[12]存储的值就不是i了,便不会出现死循环的情况。
综上,以上代码在Release版本下,编译器使变量在内存中开辟的顺序发生了变化,影响到了程序执行的结果,这便是优化带来的好处。
三.windows环境下调试介绍✨
首先第一步,我们需要将环境切换为Debug版本,才能进行调试

3.1 常用快捷键介绍
以下是在调试过程中最为常用的几个快捷键:
快捷键 | 功能 |
Ctrl+F5 | 开始执行而不进行调试。用于想让程序直接运行起来而不调试时。 |
F9 | 作用:创建断点和取消断点 断点:可以使程序在任何你想停下的地方停止执行,继而一步步执行下去。我们可以在任何地方设置断点,一个程序也可以有多个断点。 |
F10 | 逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句。 |
F11 | 逐语句,每次只执行一条语句,我们可以通过这个快捷键使我们的执行逻辑进入函数内部(这是F10所不具备的,F11是我们在调试过程中最常用的) |
F5 | 启动调试到下一断点处,需要配合F9进行使用,如果程序没有断点则无异于Ctrl+F5 |

快捷键用法
我们还可以对某一断点设置停止条件,方法是右键断点->单击条件->输入断点条件->关闭

例如:我们需要让程序停止在第4次循环处,我们可以输入i==4

此时单击F5运行到断点,我们查看自动窗口发现程序在停止时i的值为4

3.2 在调试过程中查看程序当前信息
开启调试后,我们可以在VS上方的调试->窗口看到许多用来查看数据信息的窗口:

3.3.1 监视窗口
通过监视窗口我们可以查看我们想要查看的变量甚至是表达式的值在程序运行过程中的变化,十分灵活,这是我们调试中用得最多的窗口之一。如下:


3.3.2 自动窗口
打开自动窗口后,编译器会将一些可能需要观察的变量显示在窗口中,较为方便。其缺陷是可能无法显示我们真正需要观察的变量,并且无法灵活显示表达式等的值。如下:


3.3.3 监视窗口
打开局部变量变量窗口,会将程序中的所有局部变量全部显示出来。如下:

3.3.4 调用堆栈窗口
通过调用堆栈,可以清晰的反应函数的调用关系以及当前调用所处的位置。如下:

通过调用堆栈窗口,我们可以发现,show函数栈帧在main函数栈帧之上,即show函数是由main函数调用的。并且可以看出此时show函数运行到第20行。
3.3.5 内存窗口
通过内存窗口我们可以看到内存中的信息,可以观察变量在内存中的存储情况。如下:


3.3.6 反汇编
我们可以查看当前程序转化后的汇编代码,进而从更底层的角度观察程序的执行过程。如下:

3.3.7 寄存器
通过寄存器窗口,我们可以观察在当前环境下CPU内的寄存器的使用信息,如ebp栈底寄存器、esp栈顶寄存器等等。

3.3 调试实例
我们上面通过调试分析了数组越界陷入死循环的问题。下面,我们再通过一道实例来掌握调试的技巧:
实现代码:求 1!+2!+3! ...+ n! ;不考虑溢出。我们可能会写出以下代码:
#include<stdio.h>
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:

是什么问题导致出错了呢?这就需要我们动手进行调试。在调试之前我们可以先预测问题的所在,比如算阶乘时出错、求和时出错等等。做到心里有数,而不是盲目的进行调试。
我们可以在ret*=j处设置一个断点,打开监视窗口监视ret和sum观察每个数阶乘的值和累加后的值,如下:

我们单击F10逐过程执行,当外层循环i的值为3时,即计算3的阶乘时,我们发现ret的初始值并非为1,而是2的阶乘。此时再计算3的阶乘,就是2*1*2*3=12 != 6,结果出错:

最后sum+ret的值就为15,与我们的输出相符:

可见,每次在求阶乘时,我们都应该把ret重置为1,正确的代码如下:
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;ret = 1; //将ret置1for (j = 1; j <= i; j++){ret *= j;}sum += ret;}printf("%d\n", sum);return 0;
}
3.4 实践出真知
任何事情都不可能一蹴而就,一定要多加练习,才能熟练掌握调试技巧。
一个初学者可能80%的时间在写代码,20%的时间在进行调试;而一个程序员,可能80%的时间在进行调试,剩余20%的时间才是在写代码。
随之学习的深入,后续可能会出现很多更加复杂的调试场景,如多线程等。只有打好基础,在未来才能融会贯通,利于不败之地。
多多使用快捷键可以很大程度上提高效率。
四.如何写出便于调试的优秀代码👑
4.1 什么是优秀的代码
1. 代码运行正常
2. bug很少
3. 效率高
4. 可读性高
5. 可维护性高
6. 注释清晰
7. 文档齐全
4.2 常用coding(编码)技巧
1.多使用assert断言,可以告知你出错的位置
2.尽量使用const,避免意外修改
3.养成良好的编码风格
4.添加必要的注释
5.避免编码的陷阱
4.3 实例
试模拟实现一个strcpy函数,尽量用到上述的编码技巧。如下:
char* strcpy(char* dst, const char* src) //const修饰防止对源字符串进行修改
{assert(dst && src); //避免传入空指针char cur = dst; //保存起始位置//将src的字符一个个拷贝到det中,包括'\0'while ((*dst++ = *src++)!='\0'){;}return cur; //返回目标字符串,以便链式访问
}
五.编程中常见的错误👀
5.1 编译型错误
在编译期间出现的错误。直接看错误提示信息(双击鼠标),解决问题。或者凭借经验就可以搞定。相对来说简单。
5.2 链接型错误
在链接期间出现的错误。看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不存在或者拼写错误。
5.3 运行时出错
在运行期间出现的错误。借助调试,逐步定位问题。最难搞的一种错误
不要害怕遇到错误,每出现一次的错误就是一次突破自我的机会。
学会积累排错经验,勇于尝试。不经风雨🌂,怎能见彩虹🌈
以上,就是本期的全部内容啦🌸
制作不易,能否点个赞再走呢🙏
相关文章:

VS实用调试技巧
一.什么是BUG🐛Bug一词的原意是虫子,而在电脑系统或程序中隐藏着的一些未被发现的缺陷或问题,人们也叫它"bug"。这是为什么呢?这就要追溯到一个程序员与飞蛾的故事了。Bug的创始人格蕾丝赫柏(Grace Murray H…...

通俗易懂理解三次握手、四次挥手(TCP)
文章目录1、通俗语言理解1.1 三次握手1.2 四次挥手2、进一步理解三次握手和四次挥手2.1 三次握手2.2 四次挥手1、通俗语言理解 1.1 三次握手 C:客户端 S:服务器端 第一次握手: C:在吗?我要和你建立连接。 第二次握手ÿ…...

1.1 什么是并发
1.1 什么是并发 并发:指两个或更多独立的活动同时发生。并发在生活中随处可见。我们可以一边走路一边说话,也可以两只手同时做不同的动作。 1.1.1 计算机系统中的并发 当我们提到计算机术语的“并发”,指的是在单个系统里同时执行多个独立…...

万字讲解你写的代码是如何跑起来的?
今天我们来思考一个简单的问题,一个程序是如何在 Linux 上执行起来的? 我们就拿全宇宙最简单的 Hello World 程序来举例。 #include <stdio.h> int main() {printf("Hello, World!\n");return 0; } 我们在写完代码后,进行…...
034.Solidity入门——21不可变量
Solidity 中的不可变量是在编译时就被确定的常量,也称为常量变量(constant variable)或只读变量(read-only variable)。这些变量在定义时必须立即初始化,并且在整个合约中都无法被修改,可以在函…...

Vulnhub 渗透练习(四)—— Acid
环境搭建 环境下载 kail 和 靶机网络适配调成 Nat 模式,实在不行直接把网络适配还原默认值,再重试。 信息收集 主机扫描 没扫到,那可能端口很靠后,把所有端口全扫一遍。 发现 33447 端口。 扫描目录,没什么有用的…...
C++ 在线工具
online编译器https://godbolt.org/Online C Compiler - online editor (onlinegdb.com) https://www.onlinegdb.com/online_c_compilerC Shell (cpp.sh) https://cpp.sh/在线文档Open Standards (open-std.org)Index of /afs/cs.cmu.edu/academic/class/15211/spring.96/wwwC P…...

使用MMDetection进行目标检测、实例和全景分割
MMDetection 是一个基于 PyTorch 的目标检测开源工具箱,它是 OpenMMLab 项目的一部分。包含以下主要特性: 支持三个任务 目标检测(Object Detection)是指分类并定位图片中物体的任务实例分割(Instance Segmentation&a…...

使用ThreadLocal实现当前登录信息的存取
有志者,事竟成 文章持续更新,可以关注【小奇JAVA面试】第一时间阅读,回复【资料】获取福利,回复【项目】获取项目源码,回复【简历模板】获取简历模板,回复【学习路线图】获取学习路线图。 文章目录一、使用…...

高通平台开发系列讲解(Android篇)AudioTrack音频流数据传输
文章目录 一、音频流数据传输通道创建1.1、流程描述1.2、流程图解二、音频数据传输2.1、流程描述2.2、流程图解沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章主要图解AudioTrack音频流数据传输 。 一、音频流数据传输通道创建 1.1、流程描述 AudioTrack在set函…...

BUUCTF-firmware1
题目下载:下载 新题型,记录一下 题目给出了flag形式,md5{网址:端口},下载发现是一个.bin文件 二进制文件,其用途依系统或应用而定。一种文件格式binary的缩写。一个后缀名为".bin"的文件&#x…...

【C++之容器篇】二叉搜索树的理论与使用
目录前言一、二叉搜索树的概念二、二叉搜素树的模拟实现(增删查非递归实现)1. 二叉搜素树的结点2. 二叉搜索树的实现(1). 二叉搜索树的基本结构(2)构造函数(3)查找函数(4…...
爬虫神级解析工具之XPath:用法详解及实战
一、XPATH是什么 Xpath最初被设计用来搜寻XML文档,但它同样适用于HTML文档的搜索。通过简洁明了的路径选择表达式,它提供了强大的选择功能;同时得益于其内置的丰富的函数,它可以匹配和处理字符串、数值、时间等数据格式,几乎所有节点我们都可以通过Xpath来定位。 在Pyth…...
Markdown编辑器
这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…...

数据结构<堆>
🎇🎇🎇作者: 小鱼不会骑车 🎆🎆🎆专栏: 《数据结构》 🎓🎓🎓个人简介: 一名专科大一在读的小比特,努力学习编程是我唯一…...

Linux下Socket编程利用多进程实现一台服务器与多台客户端并发通信
文章目录前言一、服务器 server二、客户端 client三、并发通信演示四、程序源码前言 前些日子同“ Linux应用编程 ”专栏中发布过的TCP及UDP在Linux或Windows下的通信都为单进程下的Socket编程,若还存在一些套接字相关函数模糊不清,读者可移步“Socket编…...

【MySQL】数据库基础
目录 1、什么是数据库 2、 数据库基本操作 2.1 查看当前数据库 2.2 创建一个数据库 2.3 选中数据库 2.4 删除数据库 3、常见的数据类型 3.1 数值类型 3.2 字符串类型 3.3 日期类型 4、表的操作 4.1 创建表 4.2 查看指定数据库下的所有表 4.3 查看表的结构 4.…...

Microsoft Office 2021 / 2019 Direct Download Links
前言 微软Office在很长一段时间内都是最常用和最受欢迎的软件。从小型创业公司到大公司,它的使用比例相当。它可以很容易地从微软的官方网站下载。但是,微软只提供安装程序,而不提供完整的软件供下载。这些安装文件通常比较小。下载并运行后,安装的文件将从后端服务器安装M…...
XX 系统oracle RAC+ADG 数据库高可用容灾演练记录
停止备库监听,避免强制关机时切换到备库 su - grid lsnrctl stop 主库高可用重启测试 /u01/app/19c/grid/bin/crsctl stop crs sync;sync;reboot --/u01/app/19c/grid/bin/crsctl start crs 机器重启后自动起的 /u01/app/19c/grid/bin/crsctl stat res -t 主库容…...

JSP与Servlet
一、什么是JSP? JSP(java Service Pages)是由Sun Microsystems公司倡导、许多公司参与一起建立的动态技术标准。 在传统的HTML文件(*.htm 、 *.html)中加入Java程序片段(Scriptlet)和JSP标签,构成了JSP网页。 1.1 JSP页面的运行原理 客户…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...

ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...