如何在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 = &num;*p = 20;printf("num=%d\n", num);return 0;
}

在这里我们需要先注意两个点:

  • p是一个变量,用来存放地址
  • *p是p指向的对象——num

根据上面所说的,那么我们可以知道的这里就有两种写法用来改变num的值:

int* p = &num;
*p = 20;  //*p是p所指向的对象int n = 1000;
p = &n;  //p是一个变量,用于存放地址
const放在*左边
const int* p = &num;
*p = 20; //error  //*p是p所指向的对象int n = 1000;
p = &n;  //p是一个变量,用于存放地址

当const放在*的左边,那么就限定的是*p,*p是不能被修改。

const放在*左边,限制的是指针指向的内容,也就是说:不能通过指针来修饰指针指向的内容。但是指针变量是可以修改的,也就是指针指向其他的变量。 

const放在*右边
int * const p = &num;
*p = 20; //*p是p所指向的对象int n = 1000;
p = &n;  //error  //p是一个变量,用于存放地址

当const放在*的右边,那么限定的就是p,p不能被修改。 

const放在*的右边,限制的是指针变量本身,指针变量不能再指向其他对象,但是可以通过指针变量修改指向的内容。

const放在*前和后
const int * const p = &num;
*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;
}

编程常见的错误

编译型错误

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

链接型错误

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

运行时错误

借助调试,逐步定位问题。也是最难搞的。

好啦~今天我们就先学到这里啦,希望大家可以帮忙纠正错误哦。祝大家国庆快乐~~~

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/98384.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【linux进程(三)】进程有哪些状态?--Linux下常见的三种进程状态

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; Linux进程 1. 前言2. 操作系统…

十五、异常(5)

本章概要 异常限制构造器 异常限制 当覆盖方法的时候&#xff0c;只能抛出在基类方法的异常说明里列出的那些异常。这个限制很有用&#xff0c;因为这意味着与基类一起工作的代码&#xff0c;也能和导出类一起正常工作&#xff08;这是面向对象的基本概念&#xff09;&#…

大话机器学习准确率(Accuracy)、精确率(Pecision)、召回率(Recall)以及TP、FP、TN、FN

话说三国时期&#xff0c;乱世出人才&#xff0c;当时刘备让张飞帮忙招兵买马&#xff0c;寻找人才。张飞发公告以后&#xff0c;有10人来面试&#xff0c;这10人分为两类&#xff0c;人才和庸才&#xff0c;各占百分之五十&#xff0c;张飞的主要作用就是从这10人中识别出人才…

UWB PDOA定位原理

以下是笔记总结,内容不完全正确 1,什么是PDOA PDOA &#xff0c;英文全称是Phase-Difference-of-Arrival&#xff0c;信号到达相位差 PDOA定位算法的原理如下&#xff1a; UWB基站上放置两个相同且间隔d<λ/2的天线&#xff0c;UWB标签上的信号到达两个天线的相位差就在-180…

Docker Cgroups资源控制

Cgroup资源控制 Docker 通过 Cgroup 来控制容器使用的资源配额&#xff0c;包括 CPU、内存、磁盘三大方面&#xff0c; 基本覆盖了常见的资源配额和使用量控制。 Cgroup 是 ControlGroups 的缩写&#xff0c;是 Linux 内核提供的一种可以限制、记录、隔离进程组所使用的物理资源…

无为WiFi的一批服务器

我们在多个地区拥有高速服务器&#xff0c;保证网速给力&#xff0c;刷片无压力 嘿嘿 <?phpinclude("./includes/common.php"); $actisset($_GET[act])?daddslashes($_GET[act]):null; $urldaddslashes($_GET[url]); $authcodedaddslashes($_GET[authcode]);he…

多无人机编队集群飞行

matlab2016b可直接运行 多无人机集群编队飞行&#xff08;8架无人机&#xff09;资源-CSDN文库

逻辑回归评分卡

文章目录 一、基础知识点(1)逻辑回归表达式(2)sigmoid函数的导数损失函数(Cross-entropy, 交叉熵损失函数)交叉熵求导准确率计算评估指标 二、导入库和数据集导入库读取数据 三、分析与训练四、模型评价ROC曲线KS值再做特征筛选生成报告 五、行为评分卡模型表现总结 一、基础知…

manual control lost 飞机乱飞

Gazebo或jmavsim里仿真都这样&#xff0c;突然QGC会出现 manual control lost&#xff0c;然后飞机会乱飞 解决方案1&#xff1a; 把 NAV_RCL_ACT 设置为 Disable&#xff0c;相当于关闭遥控器丢失失效保护&#xff0c;默认是Return返航&#xff0c;所以会乱飞。 解决方案2&a…

实体机 安装 centos

实体机 安装 centos 制作U盘的时候&#xff0c;使用的ultraISO 同样方法一个u盘制作的有问题&#xff0c; 另外一个制作的没有问题。 可能和选择 usb-hdd 或者 usb-hdd 有关 https://mirrors.tuna.tsinghua.edu.cn/centos/7.9.2009/isos/x86_64/ 参考文档&#xff1a; http:…

《Python 自动化办公应用大全》书籍推荐(包邮送书五本)

前言 随着科技的快速发展和智能化办公的需求增加&#xff0c;Python自动化办公成为了一种趋势。Python作为一种高级编程语言&#xff0c;具有简单易学、功能强大和开放源代码等优势&#xff0c;可以帮助我们更高效地完成日常办公任务。 Python自动化办公还可以帮助我们实现更…

华为数通方向HCIP-DataCom H12-831题库(单选题:221-240)

第221题 以下哪些项能被正则表达式^30.成功匹配? A、200 100 300 B、100 200 300 C、300 200 100 D、300 100 200 答案:CD 解析: 30.其中的“点”表示的是任何的一个数字,表示的是as-path的开头;所以以300开头的都是满足题目需求的。 第222题 以下哪些项的Community属性能…

厌烦了iPhone默认的热点名称?如何更改iPhone上的热点名称

你对你默认的热点名称感到厌倦了吗&#xff1f;这篇文章是为你准备的。在这里&#xff0c;你可以了解如何轻松更改iPhone上的热点名称。 个人热点会将你的手机数据转换为Wi-Fi信号。手机上的个人热点使用户能够与其他用户共享其蜂窝数据连接。当你在WIFI网络之外时&#xff0c…

时序预测 | MATLAB实现ICEEMDAN-IMPA-GRU时间序列预测

时序预测 | MATLAB实现ICEEMDAN-IMPA-GRU时间序列预测 目录 时序预测 | MATLAB实现ICEEMDAN-IMPA-GRU时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 ICEEMDAN-IMPA-GRU功率/风速预测 基于改进的自适应经验模态分解改进海洋捕食者算法门控循环单元时间序列预…

通过IP地址管理提升企业网络安全防御

在今天的数字时代&#xff0c;企业面临着越来越多的网络安全威胁。这些威胁可能来自各种来源&#xff0c;包括恶意软件、网络攻击和数据泄露。为了提高网络安全防御&#xff0c;企业需要采取一系列措施&#xff0c;其中IP地址管理是一个重要的方面 1. IP地址的基础知识 首先&a…

常见弯道输送机有哪些

提到弯道输送机您可能首先想到的就是弯道滚筒线&#xff0c;其实除了滚筒线之外&#xff0c;也有一些其他线体可以做弯道&#xff0c;下面就为您总结了4种常见的弯道输送机。 1、弯道皮带线&#xff1a;即线体转弯处设计成皮带输送机&#xff0c;这种形式的转弯设计可以实现不同…

【ElasticSearch】基于 Java 客户端 RestClient 实现对 ElasticSearch 索引库、文档的增删改查操作,以及文档的批量导入

文章目录 前言一、对 Java RestClient 的认识1.1 什么是 RestClient1.2 RestClient 核心类&#xff1a;RestHighLevelClient 二、使用 Java RestClient 操作索引库2.1 根据数据库表编写创建 ES 索引的 DSL 语句2.2 初始化 Java RestClient2.2.1 在 Spring Boot 项目中引入 Rest…

ChatGPT多模态升级,支持图片和语音,体验如何?

一、前言 9 月 25 日&#xff0c;ChatGPT 多模态增加了新的语音功能和图像功能。这些功能提供了一种新的、更直观的界面&#xff0c;允许我们与 ChatGPT 进行语音对话或展示我们正在谈论的内容。 ChatGPT 现在可以看、听、和说话了&#xff0c;而不单单是一个文本驱动的工具了。…

Android攻城狮学鸿蒙 -- 点击事件

具体参考&#xff1a;华为官网学习地址 1、点击事件&#xff0c;界面跳转 对于一个按钮设置点击事件&#xff0c;跳转页面。但是onclick中&#xff0c;如果pages前边加上“/”&#xff0c;就没法跳转。但是开发工具加上“/”才会给出提示。不知道是不是开发工具的bug。&#…

Charles:移动端抓包 / windows客户端 iOS手机 / 手机访问PC本地项目做调试

一、背景描述 1.1、本文需求&#xff1a;移动端进行抓包调试 1.2、理解Charles可以做什么 Charles是一款跨平台的网络代理软件&#xff0c;可以用于捕获和分析网络流量&#xff0c;对HTTP、HTTPS、HTTP/2等协议进行调试和监控。使用Charles可以帮助开发人员进行Web开发、调试…