如何在VS2022中进行调试bug,调试的快捷键,debug与release之间有什么区别
什么是bug
在学习编程的过程中,应该都听说过bug吧,那么bug这个词究竟是怎么来的呢?
其实Bug的本意是“虫子”或者“昆虫”,在1947年9月9日,格蕾丝·赫柏,一位为美国海军工作的电脑专家,也是最早将人类语言融入到电脑程序的的人之一,这天他对Harvard Mark II 设置好了17000个继电器进行编程之后,技术人员正在进行整机运行时,它突然停止了工作。于是它们爬进去找原因,发现这台巨大的计算机内部有一组继电器的触点之间有只飞蛾。之后,赫柏用胶条贴上飞蛾,并把“bug”来表示“一个在电脑程序里的错误”,“bug”这个说法一直沿用到今天。

调试的重要性
在我们初学阶段,可能并不太会注意调试,代码遇到问题可能就是简单看看逻辑问题。但是如果遇到了一些难以寻找的问题时,调试是真的一个很不错的方法让你快速的找到你的bug。
调试的基本步骤
- 发现程序的错误的存在
- 以隔离、消除等方式对错误进行定位
- 确定错误产生的原因
- 提出纠正错误的解决办法
- 对程序错误予以改正,重新测试
debug和release介绍
在VS2022中,有两种不同的版本,一种叫做Debug版本,一种叫做Release版本。而这两种的本质区别就是release版本是不能调试的。
Debug版本
Debug版本,通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。Debug版本就是为调试而生的,编译器在Debug版本中会假如调试辅助信息的,并且很少会进行优化,代码还是“原汁原味”的。
Release版本
Release版本,通常称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好的使用。Release 是“发行”的意思,Release 版本就是最终交给用户的程序,编译器会使尽浑身解数对它进行优化,以提高执行效率,虽然最终的运行结果仍然是我们期望的,但底层的执行流程可能已经改变了。编译器还会尽量降低 Release 版本的体积,把没用的数据一律剔除,包括调试信息。最终,Release 版本是一个小巧精悍、非常纯粹、为用户而生的程序。
虽说两种版本有些差别,但是它们之间运行的结果是一样的。那么,它们之间的区别究竟在哪呢?为了直观一点,我们看一下下面的图片,
同一种代码,在不同的版本下,会生成各自的文件夹以及exe程序,因为debug版本,会包含各种调试辅助信息,所以在debug版本下生成的文件的内存是大于在release版本下的内存的。
Windows环境调试介绍
调试的快捷键
Ctrl+F5
开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用。
F10
逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句。
F11
逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部。
F9
创建断点和取消断点。
断点的重要作用,可以在程序的任意位置设置断点。
这样就可以使得程序在想要的位置随意停止执行,继而一步步执行下去。
F5
启动调试,经常用来直接跳到下一个断点处。
断点的使用还有一些小的技巧,如果说是假设还是上面的代码,想要在i=5时,就让其程序停止执行,那么先在那一条语句中设置断点,随后单击右键,点击“条件”,并且设置所需要的情况条件即可。
调试的时候查看程序当前信息
下面这些功能,必须是在开始调试之后,才能在窗口中看到这些信息。
自动窗口
但是,这种自动窗口是有些弊端,让我们不能持续地关注到一个数值。其实我们更想要随意地观察一个数值,常用的一个窗口是“监视”。
监视窗口
根据自己的需求,可以打开多个窗口。
内存
调用堆栈
调用堆栈,反应的就是函数的调用逻辑。例如,将下面的代码,在调用堆栈中观察:
#include <stdio.h>void test2()
{printf("test2\n");
}void test1()
{test2();
}void test()
{test1();
}int main()//main函数也是被其他函数调用的
{test();return 0;
}
- 在学习完了调试一些小技巧之后,小编要给大家来建议一些事情啦~
- 多多动手,尝试调试,才能有进步。
- 一定要掌握调试的技巧。
- 初学者可能在80%的时间在写代码,20%的时间在调试。但是一个程序员可能是在20%的时间在写程序,但是80%的时间在调试。
- 我们目前不懈的都是简单代码的调试。
- 但是在以后可能会出现很复杂的调试场景:多线程程序的调试等。
- 多多使用快捷键,提升效率。
一些调试的实例
接下来,我们看几个实例,并且利用调试来分析其中的错误在哪。
实例一:求1!+2!+3!+...+n!,不考虑溢出。
#include <stdio.h>
int main()
{int i = 0;int sum = 0;//保存最终结果int n = 0;int ret = 1;scanf("%d", &n);//输入3//1!+2!+3!=1+2+6=9for (i = 1; i <= n; i++)//n项之和{int j = 0;for (j = 1; j <= i; j++){ret *= j;}sum += ret;}printf("%d\n", sum);return 0;
}
本应该在这个程序中输入“3”,其结果应该是“9”,为何会是“15”呢?
经过调试之后,可以观察到:其实是ret的值,没有在适当的位置进行初始化。
实例二:研究程序死循环的原因。
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;
}
注意,这道题仅限在VS上X86的Debug模式下验证和讲解的。
我们在调试的过程中,竟然发现,arr[12]与 i 同时发生变化,并且 arr[12]与 i 的存储地址也是一样的。
那么这里,就要讲一下这个内存的创建变量了。
- 栈区上内存使用习惯是:从高地址向低地址处使用
- 正常情况下:i 先创建,arr后创建
- i的地址一定是大于arr地址
- 数组随着下标的增长,地址是由低到高变化的
- 这个代码中i和arr之间空2个整型,完全是巧合,取决于编译器
而在Release版本中,它会自动优化,那么如何优化呢?例如下图,让其打印出在Release版本下的数组以及i的地址:
我们可以清晰地观察到 i 的地址是比 arr 数组的地址小, 所以Release版本在这样情况下,优化了两者之间的地址。
如何写出好的代码
优秀的代码
- 代码运行正常
- bug很少
- 效率高
- 可读性高
- 可维护性高
- 注释清晰
- 文档齐全
常见的coding技巧
- 使用assert断言
- 尽量使用const
- 养成良好的编码风格
- 添加必要的注释
- 避免编码的陷阱
- 编程常见的错误
实例三:模拟实现strcpy库函数
我们先学习一下这个strcpy库函数是如何使用的。
【strcpy函数】:
- function
- 函数原型:char * strcpy ( char * destination , const char * source ) ;//传参传入地址
- 函数原型:函数名、参数、返回类型
- strcpy功能:从源头拷贝到目的地
- destination is returned.将目标地址返回来
- including the terminating null character(and stopping at the point)包括‘\0’
- strcpy函数需要头文件[string.h]
- 库函数strcpy返回的是目标空间的起始地址
我们先练习一下这个函数的使用。
#include <stdio.h>
#include <string.h>
int main()
{char arr1[20] = { 0 };char arr2[] = "hello bit";strcpy(arr1, arr2);printf("%s\n", arr1);return 0;
}
模拟实现strcpy函数,假设这个新的函数命名是my_strcpy,要注意在模拟的过程中,我们的函数原型是要保持一致的,所以函数声明为void my_strcpy(char * dest , char * src);
要将是src中所有的字符串都复制到dest中,那么肯定需要遍历的方法让其循环,直到将'\0'也复制过去。
my_strcpy(char* dest, char* src)
{//将源头的字符串拷贝到目的地的空间里面去while (*src!='\0'){*dest = *src;dest++;src++;}*dest = *src;
}int main()
{char arr1[20] = "xxxxxxxxxxxxxxx";char arr2[] = "hello bit";my_strcpy(arr1, arr2);printf("%s\n", arr1);//打印出来arr1只是‘hello bit’,是因为‘\0’也被拷贝进去了,使其字符串结束了return 0;
}
而这里我们要注意,我们是为了写出更好的代码,所以我们再次完善上面的代码。
在上面的代码中,我们可以利用后置加加,将这个代码简化一下:
while (*src!='\0'){*dest++ = *src++;//后置加加:先使用后加假}
我们可以知道,上面的代码,是分成了两次拷贝的,第一次是遇到‘\0’的时候,拷贝‘\0’之前的,另一次是拷贝‘\0’,这样这个代码并不是那么高效,我们能不能让它一次就将‘\0’也拷贝进去呢?
while (*dest++ = *src++)//利用ASCII码值,直到将'\0'拷贝进去之后,因为'\0'的ASCII码值是0,所以为假{;}
在经过上面的修缮之后,我们现在的完整代码如下:
my_strcpy(char* dest, char* src)
{while (*dest++ = *src++){;}
}int main()
{char arr1[20] = "xxxxxxxxxxxxxxx";char arr2[] = "hello bit";my_strcpy(arr1, arr2);printf("%s\n", arr1);return 0;
}
那么如果arr1中传入了一个空指针呢?要怎么办呢?
my_strcpy(NULL, arr2);
assert断言的使用
所以我们需要判断一下,是否为空指针。判断空指针有两种方法:
第一种就是简单的if语句,但是这里就是会使得if语句一直执行,效率很低。
if(dest == NULL || src == NULL)
第二种就是assert宏的使用:
- assert(条件),如果条件为假,那么程序会直接报错。
- assert的头文件:assert.h
- 在release版本中,代码会自动优化assert。
assert(dest != NULL);
assert(src != NULL);
然后,要注意的是strcpy函数返回的是是目标空间的起始地址。所以如下代码:
char * my_strcpy(char* dest, char* src)
{char * ret = dest;assert(dest!=NULL);assert(src!=NULL);while (*dest++ = *src++){;}return ret;
}int main()
{char arr1[20] = "xxxxxxxxxxxxxxx";char arr2[] = "hello bit";my_strcpy(arr1, arr2);printf("%s\n", arr1);return 0;
}
const修饰变量
我们在这里了解一下const有什么作用:const修饰变量的时候,是在语法层面上限制了const修改。但是本质上,num还是变量,是一种不能被修改的变量。
const修饰变量
const修饰指针
我们现在用下面的例子解释:
#include<stdio.h>
int main()
{const int num = 10;printf("num=%d\n", num);int* p = #*p = 20;printf("num=%d\n", num);return 0;
}
在这里我们需要先注意两个点:
- p是一个变量,用来存放地址
- *p是p指向的对象——num
根据上面所说的,那么我们可以知道的这里就有两种写法用来改变num的值:
int* p = #
*p = 20; //*p是p所指向的对象int n = 1000;
p = &n; //p是一个变量,用于存放地址
const放在*左边
const int* p = #
*p = 20; //error //*p是p所指向的对象int n = 1000;
p = &n; //p是一个变量,用于存放地址
当const放在*的左边,那么就限定的是*p,*p是不能被修改。
const放在*左边,限制的是指针指向的内容,也就是说:不能通过指针来修饰指针指向的内容。但是指针变量是可以修改的,也就是指针指向其他的变量。
const放在*右边
int * const p = #
*p = 20; //*p是p所指向的对象int n = 1000;
p = &n; //error //p是一个变量,用于存放地址
当const放在*的右边,那么限定的就是p,p不能被修改。
const放在*的右边,限制的是指针变量本身,指针变量不能再指向其他对象,但是可以通过指针变量修改指向的内容。
const放在*前和后
const int * const p = #
*p = 20; //error //*p是p所指向的对象int n = 1000;
p = &n; //error //p是一个变量,用于存放地址
这里两种都会报错,都不能被修改。
所以为了上面的strcpy函数中,它的源头地址里的内容,是不能被修改的,所以利用const,将上面的完善一下就是:
char * my_strcpy(char* dest, const char* src)
{char * ret = dest;assert(dest!=NULL);assert(src!=NULL);while (*dest++ = *src++){;}return ret;
}int main()
{char arr1[20] = "xxxxxxxxxxxxxxx";char arr2[] = "hello bit";my_strcpy(arr1, arr2);printf("%s\n", arr1);return 0;
}
实例四:模拟实现strlen函数
#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)
{assert(str != NULL);//起初让这个end末尾的值与str起始值相等//让这个end值一直++,直到为‘\0’,结束//让这个end-起始值-1'\0'就是strlen的长度了const char* end = str;while (* end++){;}return end - str - 1;
}
int main()
{char arr[] = "abcdef";size_t len = my_strlen(arr);printf("%zd\n", len);return 0;
}
编程常见的错误
编译型错误
直接看错误提示信息(双击),解决问题。或者是凭借经验就可以搞定。相对来说简单。
链接型错误
一般是标识符名不存在或者是拼写错误。
运行时错误
借助调试,逐步定位问题。也是最难搞的。
好啦~今天我们就先学到这里啦,希望大家可以帮忙纠正错误哦。祝大家国庆快乐~~~
相关文章:

如何在VS2022中进行调试bug,调试的快捷键,debug与release之间有什么区别
什么是bug 在学习编程的过程中,应该都听说过bug吧,那么bug这个词究竟是怎么来的呢? 其实Bug的本意是“虫子”或者“昆虫”,在1947年9月9日,格蕾丝赫柏,一位为美国海军工作的电脑专家,也是最早…...

初识jmeter及简单使用
目录 1、打开页面: 2、添加线程组: 3、线程组中设置参数: 4、添加请求 5、添加一个http请求后,设置请求内容 6、添加察看结果树 7、执行,查看结果 一般步骤是:在测试计划下面新建一个线程组…...

Spring 在多线程环境下如何确保事务一致性
问题在现 如何解决异步执行 多线程环境下如何确保事务一致性 事务王国回顾 事务实现方式回顾 编程式事务 利用编程式事务解决问题 问题分析完了,那么如何解决问题呢? 小结 问题在现 我先把问题抛出来,大家就明白本文目的在于解决什…...
[Machine Learning] Learning with Noisy Data
文章目录 Probabilistic Perspective of NoiseBias and VarianceRobustness among Surrogate Loss FunctionsNMF Probabilistic Perspective of Noise 假设数据来源于一个确定的函数,叠加了高斯噪声。我们有: y h ( x ) ϵ y h(x) \epsilon yh(x)ϵ…...
C++中有哪些常用的标准库?
C中有许多常用的标准库,这些库提供了丰富的功能和工具,方便开发人员进行各种任务。以下是一些常见的C标准库: iostream:用于输入和输出操作,包括cin、cout和cerr等类和函数。algorithm:提供了许多常用的算…...
软考-信息安全工程师概述
本文为作者学习文章,按作者习惯写成,如有错误或需要追加内容请留言(不喜勿喷) 本文为追加文章,后期慢慢追加 2023年10月 信息考试大纲 通过本考试的合格人员能够掌握网络信息安全的基础知识和技术原理,…...

2023-2024年华为ICT网络赛道模拟题库
2023-2024年网络赛道模拟题库上线啦,全面覆盖网络,安全,vlan考点,都是带有解析 参赛对象及要求: 参赛对象:现有华为ICT学院及未来有意愿成为华为ICT学院的本科及高职院校在校学生。 参赛要求:…...

英特尔参与 CentOS Stream 项目
导读红帽官方发布公告欢迎英特尔参与进 CentOS Stream 项目,并表示 “这一举措不仅进一步深化了我们长期的合作关系,也构建在英特尔已经在 Fedora 项目中积极贡献的基础之上。” 目前,CentOS Stream 共包括以下特别兴趣小组(SIG&a…...
Centos 服务器 MySQL 8.0 快速开启远程访问
环境: MySQL 8.0(低版本会有些不同), Rocky Linux 9.0(CentOS) 直接上干货,相信大家看到这个文章的时候都已经安装完了。 1. 先从服务器上使用 root 进行登录(刚安装完默认只能本地…...

充电保护芯片TP4054国产替代完全兼容DP4054DP4054H 锂电充电芯片
■产品概述 DP4054H是-款完整的采用恒定电流/恒定电压单节锂离子电池充电管理芯片。其SOT小封装和较少的外部元件数目使其成为便携式应用的理想器件,DP4054H可 以适合USB电源和适配器电源工作。 由于采用了内部PMOSFET架构,加上防倒充电路,所以不需要外…...
Java Spring Boot中的爬虫防护机制
随着互联网的发展,爬虫技术也日益成熟和普及。然而,对于某些网站来说,爬虫可能会成为一个问题,导致资源浪费和安全隐患。本文将介绍如何使用Java Spring Boot框架来防止爬虫的入侵,并提供一些常用的防护机制。 引言&a…...
状态模式 行为型模式之六
1.定义 允许一个对象在其对象内部状态改变时改变它的行为。 2.组成结构 Context:定义客户感兴趣的接口;维护一个ConcreteState子类的实例,这个实例定义当前的状态。State:定义一个接口来封装Context的与特定状态相关的行为。Co…...

JAVA NIO深入剖析
4.1 Java NIO 基本介绍 Java NIO(New IO)也有人称之为 java non-blocking IO是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方…...

企业电子招投标系统源码之电子招投标系统建设的重点和未来趋势
功能描述 1、门户管理:所有用户可在门户页面查看所有的公告信息及相关的通知信息。主要板块包含:招标公告、非招标公告、系统通知、政策法规。 2、立项管理:企业用户可对需要采购的项目进行立项申请,并提交审批,查看所…...

基于正点原子alpha开发板的第三篇系统移植
系统移植的三大步骤如下: 系统uboot移植系统linux移植系统rootfs制作 一言难尽,踩了不少坑,当时只是想学习驱动开发,发现必须要将第三篇系统移植弄好才可以学习后面驱动,现将移植好的文件分享出来: 仓库&…...

数据结构与算法设计分析——贪心算法的应用
目录 一、贪心算法的定义二、贪心算法的基本步骤三、贪心算法的性质(一)最优子结构性质(二)贪心选择性质 四、贪心算法的应用(一)哈夫曼树——哈夫曼编码(二)图的应用——求最小生成…...
Leetcode 2895. Minimum Processing Time
Leetcode 2895. Minimum Processing Time 1. 解题思路2. 代码实现 题目链接:2895. Minimum Processing Time 1. 解题思路 这一题整体上来说其实没啥难度,就是一个greedy算法,只需要想明白耗时长的任务一定要优先执行,不存在某个…...

学信息系统项目管理师第4版系列21_范围管理
1. 产品范围 1.1. 某项产品、服务或成果所具有的特征和功能 1.2. 产品范围的完成情况是根据产品需求来衡量的 1.3. “需求”是指根据特定协议或其他强制性规范,产品、服务或成果必须具备的条件或能力 2. 项目范围 2.1. 包括产品范围,是为交付具有规…...

threejs 透明贴图,模型透明,白边
问题 使用Threejs加载模型时,模型出现了上面的问题。模型边缘部分白边,或者模型出现透明问题。 原因 出现这种问题是模型制作时使用了透明贴图。threejs无法直接处理贴图。 解决 场景一 模型有多个贴图时(一个透贴和其他贴图࿰…...

CCF CSP认证 历年题目自练Day21
题目一 试题编号: 201909-1 试题名称: 小明种苹果 时间限制: 2.0s 内存限制: 512.0MB 题目分析(个人理解) 先看输入,第一行输入苹果的棵树n和每一次掉的苹果数m还是先如何存的问题…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...

XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...