《C语言深度解剖》(8):一篇文章彻底学会Visual Studio 调试技巧,新手必看!

🤡博客主页:醉竺

🥰本文专栏:《C语言深度解剖》

😻欢迎关注:感谢大家的点赞评论+关注,祝您学有所成!


✨✨💜💛想要学习更多数据结构与算法点击专栏链接查看💛💜✨✨ 


1. 什么是bug?

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

2. 调试是什么?有多重要? 

所有发生的事情都一定有迹可循,如果问心无愧,就不需要掩盖也就没有迹象了,如果问心有愧, 就必然需要掩盖,那就一定会有迹象,迹象越多就越容易顺藤而上,这就是推理的途径。 顺着这条途径顺流而下就是犯罪,逆流而上,就是真相。 

一名优秀的程序员是一名出色的侦探。 

每一次调试都是尝试破案的过程。

我们是如何写代码的? 

又是如何排查出现的问题的呢? 

下面进入正题! 

2.1 调试是什么? 

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

2.2 调试的基本步骤

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

2.3 Debug和Release的介绍 

Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。 Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优 的,以便用户很好地使用。 

代码:

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

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

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

Debug和Release反汇编展示对比:

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

那编译器进行了哪些优化呢? 请看如下代码:

#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 模式去编译,程序的结果是死循环。

如果是 release 模式去编译,程序没有死循环。

那他们之间有什么区别呢? 就是因为release模式优化导致的。 

变量在内存中开辟的顺序发生了变化,影响到了程序执行的结果。 

上述代码在下面调试案例中会详细讲解,这里只是简单说一些该程序在Debug模式和Release模式下的区别。

3. Windows环境调试介绍

注:linux开发环境调试工具是gdb,后面我会单独开一个专栏进行学习。

3.1 调试环境的准备 

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

3.2 学会快捷键 

最常用的调试快捷键其实就5个: 

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

3.2.1 演示CTRL + F5

3.2.2. 演示F10和F11 

在着手调试程序时,开发者通常会依赖于F10和F11这两个关键的快捷键。这两个键的主要区别在于它们对函数调用的处理方式。

  1. 当你按下F11时,调试器会进入函数的内部,允许你逐行执行函数中的代码,这被称为“单步执行进入”。这种方式非常适合于深入理解函数的具体行为和逻辑。
  2. 相对地,F10键用于“单步执行”,当遇到函数调用时,它不会进入函数内部,而是将整个函数视为一个步骤来执行。这意味着它会直接跳过函数的内部细节,只显示函数调用的结果。这种方法在你知道函数已经正常工作,或者不关心函数内部细节时非常有用。
  3. 总结来说,F11用于深入函数内部进行详细调试,而F10则用于快速遍历代码而不深入每个函数的细节。合理使用这两个快捷键可以大大提高调试效率。

接下来演示F10和F11实际中的区别:

  • 按下 F10 或者 F11进入调试过程

  • 按F10,执行 test() 函数

我们发现,按F10逐过程遇到test()函数时,会直接显示该函数运行的最终结果并不会进入test()函数的内部细节。

  

我们看看如果运行到test()函数时,按F11是怎样的 

  • 按F11,执行 test() 函数

我们发现,按F11逐语句遇到test()函数时,调试器箭头会进入该函数的内部,允许我们逐条执行并函数内部的代码细节。直到该函数执行完毕,然后箭头跳转到main()函数中该函数调用语句的下一条指令。


3.2.3 演示F5和F9

F5和F9通常是配合着使用的。 

在处理较长的程序时,手动逐句执行到特定代码行会非常耗时。为了提高效率,我们通常会使用断点来标记我们关心的代码行。

  1. 按下F9键,这样可以在特定的代码行上设置一个标记,指示调试器在该行暂停执行。如果需要取消断点,再次按下F9键即可。 
  2. 一旦断点设置完毕,我们可以通过按下F5键启动调试。程序将开始执行,直到遇到第一个断点。
  3. 如果程序中有多个断点,我们可以继续按下F5键,让程序从一个断点跳转到下一个断点
  4. 这时,执行将暂停,允许我们检查当前状态、变量值以及其他相关的调试信息。
  5. 这样就可以只关注那些我们怀疑可能存在问题或者想要深入了解的代码区域。

下面这张图是一个断点的情况:

下面这张图是多个断点的情况: 

1.分别在13行和22行按 F9 设置两个断点

2.按下F5键,程序跳转到第一个断点处,并且断点之前的代码都已执行 

3.继续按下F5,程序从第一个断点处跳到第二个断点处,第二个断点之前的程序都已执行。

补充1:条件断点 

在调试过程中,我们可以通过为断点添加条件来进一步细化控制程序执行的行为。这样,即使程序运行到该断点,也不会立即暂停,除非满足我们设定的条件。设置条件断点的方法通常是在断点属性中进行配置,这可以通过右键点击断点并选择“条件”来设置。 

例如,假设我们有一个循环,我们只想在循环变量达到特定值时才暂停执行。我们可以在循环内的代码行上设置一个断点,并为其添加一个条件,比如“当循环变量等于某个值时暂停”。这样,当我们按下F5键启动调试时,程序将正常运行,直到循环变量满足我们设定的条件,此时程序会在该断点处暂停。 

下面将演示条件断点的设置:在for循环中,我们在设置i==5时,程序会停下来。

具体步骤:1.按F9设置断点 —>2.鼠标右键断点—>3.点击条件并设置—>4.按F5执行调试—>5.执行到条件断点处程序停下。

补充2:查看断点的数量和信息 

当程序特别长有设置了特别多的断点时,我们想查看所有断点的数量和信息可以按照以下步骤:

点击 1.调试 —> 2.窗口—>3.断点 

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

调试的时候除了上述的几个快捷键,最重要的就是使用调试窗口中的几个功能。如下图所示:

注意:下面窗口的功能,首先进入调试中才会有,正常运行程序是不会有的。 

3.3.1 自动窗口和临时变量窗口(了解)

点击自动窗口或者局部变量窗口,逐步调试时,自动窗口和局部窗口中会短暂的自动展示一些变量的值,自动窗口中包括全局变量以及局部变量;局部变量窗口则只展示局部变量的值。但是继续调试过程中,两个窗口编译器就不再显示变量值,我们就无法观察了。所以说这两个窗口并不实用也不常用,这里只是简单提一下。

后续主要用的就是监视窗口。

1.自动变量窗口 

2.局部变量窗口 

3.3.2 查看变量的值 

点击“监视”,可以打开好几个窗口,同时窗口的位置可以长按拖动。 

监视窗口可以添加你想观察的成员信息,包括并不限于各种变量,数组,结构体,指针等,以及它们的地址。如下图所示:

3.3.2 查看内存信息 

内存窗口显示的内容所代表的具体含义:

3.3.3 查看调用堆栈  

当多个函数嵌套调用的时候,如果想理清每个函数之间的关系,可以查看调用堆栈窗口 

下面请看函数栈的堆叠以及释放:

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

3.3.4 查看汇编信息 

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

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

就能看到汇编代码了:

(2)第二种方式:

3.3.5 查看寄存器信息  

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

3.3.6 小细节补充

在C或C++等编程语言中,数组作为函数参数传递时,它们会退化为指针。这意味着数组不再携带其原始的长度信息,因此在调试器的监视窗口中查看这样的参数时,我们无法直接看到整个数组的细节。

解决方法: arr,number

4.多动手,尝试调试,才能进步

  • 一定要熟练掌握调试技巧。
  • 初学者可能80%的时间在写代码,20%的时间在调试。
  • 但是一个程序员可能20%的时间在写程序,但是80%的时间在调试。
  • 我们所讲的都是一些简单的调试。
  • 以后可能会出现很复杂调试场景:多线程程序的调试等。
  • 多多使用快捷键,提升效率。 

5. 调试实战 

5.1 实例一 

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

下面代码语法没问题,但是逻辑有问题,我们需要调试查出来: 

这时候我们如果3,期待输出9,但实际输出的是15。 why? 这里我们就得找我们问题。 

1. 首先推测问题出现的原因。初步确定问题可能的原因最好。

2. 实际上手调试很有必要。

3. 调试的时候一些中间过程和结果我们至少是心里有数的。 

所以正确代码应该是:

5.2 实例二 

看下面这段代码有没有什么问题?运行结果是什么? 

运行结果:程序陷入了死循环!

这是怎么回事?

我们第一眼观察到这个代码的时候,首先观察到的应该是“数组的越界访问”,因为这类数组只有10个数,下标访问应该到9。一般情况下越界访问应该直接会报错,非法访问,但是这里为什么会陷入死循环呢?

所以我们需要对程序进行调试,研究程序死循环的原因。 

在上面调试过程中,我们发现了一个现象,i 从 1 变成 12 的过程中,arr[12]的数值跟i一模一样!

因此,我猜想 i 和 arr[12] 处于同一块空间?例如下图这样?

为了验证这个猜想,在监视窗口中,添加 “&i” 和 “&arr[]12”,对比一下它们俩的地址。请看下图:

我们发现, i 和 arr[12] 确实处于同一块空间,因此当arr[12] 赋值为0的时候,i 也变成了 0.因此for循环从0又开始继续增长,最后陷入了死循环。

为什么会这样呢?为什么 i 和arr[12] 处于同一块空间? 

拓展: 

上述代码的运行结果其实是跟编译环境有关系的。 

这个例子呢并不是探究编译器的内存分配的,不过这个确实是一个利用调试技巧来探究代码运行结果的好例子。


6. 如何写出好(易于调试)的代码 

6.1 优秀的代码: 

1. 代码运行正常

2. bug很少

3. 效率高

4. 可读性高

5. 可维护性高

6. 注释清晰

7. 文档齐全 

常见的coding技巧: 

1. 使用assert

2. 尽量使用const

3. 养成良好的编码风格

4. 添加必要的注释

5. 避免编码的陷阱。 

这里先简单介绍一个assert和const的用法:

6.1.1 assert

在C语言中,assert宏定义在头文件assert.h中。要使用它,需要在源文件顶部包含这个头文件: 

#include <assert.h>

assert宏的原型如下: 

void assert(int expression);

它接收一个表达式,并检查表达式的值是否为非零(true)。如果表达式的计算结果为0(即为false),assert将输出一条错误消息到标准错误输出,并终止程序执行。 

6.1.2 const的作用

结论:

const修饰指针变量的时候: 

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

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

// const修饰指针变量的时候://代码1 测试无cosnt的
void test1()
{int n = 10;int m = 20;int* p = &n;*p = 20; // rightp = &m; // right
}//代码2 测试const放在*的左边
void test2()
{//代码2int n = 10;int m = 20;const int* p = &n;*p = 20; // errorp = &m; // right
}//代码3 测试const放在*的右边
void test3()
{int n = 10;int m = 20;int* const p = &n;*p = 20; // rightp = &m;  // error
}//代码4 测试const放在*的两边
void test4()
{int n = 10;int m = 20;const int* const p = &n;*p = 20; // errorp = &m;  // error
}

6.2 示范: 

模拟实现库函数:strcpy 

#include<assert.h>// 原版
char* my_strcpy(char* dest, const char* src) 
{assert(dest != NULL);assert(src != NULL);while (*src != '\0'){*dest = *src;dest++;src++;}*dest = *src; // 拷贝'\0'结束符return dest;
}// 1.优化断言
char* my_strcpy(char* dest, const char* src)
{assert(dest && src);while (*src != '\0'){*dest = *src;dest++;src++;}*dest = *src; // 拷贝'\0'结束符return dest;
}// 2.优化赋值
char* my_strcpy(char* dest, const char* src)
{assert(dest && src);while (*src != '\0'){*dest++ = *src++;}*dest = *src; // 拷贝'\0'结束符return dest;
}// 3.优化循环和拷贝'\0'结束符
char* my_strcpy(char* dest, const char* src)
{assert(dest && src);while (*dest++ = *src++){;}return dest;
}        // 4.优化返回值
char* my_strcpy(char* dest, const char* src)
{assert(dest && src);char* ret = dest;while (*dest++ = *src++){;}return ret;
}
int main()
{char str1[] = "hello world";char str2[100];my_strcpy(str2, str1);printf("%s\n", str2);return 0;
}

模拟实现一个strlen函数
参考代码: 

#include<assert.h>size_t my_strlen(const char* str)
{assert(str != NULL);size_t len = 0;while (*str) // 判断字符串是否结束{len++;str++;}return len;
}int main()
{char str[] = "hello world";size_t len = my_strlen(str);printf("len = %d\n", len);return 0;
}

7. 编程常见的错误

7.1 编译型错误

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

7.2 链接型错误

看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不 存在或者拼写错误。

7.3 运行时错误

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

温馨提示: 做一个有心人,积累排错经验。 

以上就是visual stdio 进行调试的教程了,后续会专门出一个visual 调试的专栏,以及Linux环境下GDB调试的专栏,会有更加高阶的调试技巧!敬请期待!

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

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

相关文章

MySQL 基础语法(2)

文章目录 创建表查看表修改表表数据插入 本文为表结构相关的基础语言库相关的基础语句 创建表 CREATE TABLE table_name ( field1 datatype comment xxx, field2 datatype, field3 datatype ) character set 字符集 collate 校验规则 engine 存储引擎;CREATE TABLE&#xff1…

NLP(2)--搭建简单的模型(nn)

前言 仅记录学习过程&#xff0c;有问题欢迎讨论 可能使用到的包 可以通过Anaconda直接install&#xff0c;不行就PIP install xxx PythonTorchTransformersScikit-learnNumpyGensimPandas 我的版本&#xff1a; 可以用 conda list 查看 代码 如果有包导入不进来&…

如何搭建私域获取淘宝店铺卖家订单信息trade.fullinfo.get

私域流量主要由已经对企业或品牌感兴趣并进行关注的用户组成&#xff0c;这些用户对企业具有一定的忠诚度和粘性。通过与这些用户建立良好的互动和关系&#xff0c;企业可以进一步提升用户的忠诚度和转化率&#xff0c;从而有助于建立持续稳定的业务模式和盈利模式&#xff0c;…

【ARFoundation自学01】搭建AR框架+检测平面+点击克隆立方体到地面=自信入门!

介绍 AR 的功能其实是个大手机系统厂商和眼镜设备厂商开发的功能&#xff0c;并不是Unity的功能&#xff0c;毕竟Unity没有自己的手机设备&#xff01;比如谷歌公司的安卓开发了ARcore&#xff0c;让所有安卓8.0版本以上的用户能够在手机上体验AR功能&#xff01;苹果推出了AR…

2024红明谷杯——Misc 加密的流量

2024红明谷杯——Misc 加密的流量 写在前面&#xff1a; 这里是贝塔贝塔&#xff0c;照例来一段闲聊 打比赛但赛前一波三折&#xff0c;又是成功签到的一个比赛 说起来比赛全名叫红明谷卫星应用数据安全场景赛&#xff0c;但好像真的跟卫星的关系不大&#xff0c;没有bin方…

深入探索Python中的推导式:从列表到字典,全面解析数据结构的快速构建方法

文章目录 1. 列表推导式&#xff1a;快速构造列表1.1 基础用法1.2 条件筛选 2. 字典推导式&#xff1a;动态构建字典2.1 基础用法2.2 使用条件过滤 3. 集合推导式&#xff1a;有效去重与数据筛选3.1 基本语法与应用3.2 去重和转换3.2 使用条件过滤 4. 生成器推导式&#xff1a;…

基于CH32V103的多功能推杆设计

一、项目简介 “创意源于生活&#xff0c;工具始于懒惰。” 整体造型外观参考了最近比较火的夫妻游戏《双人成行》第一关里面那个吸尘器的推杆开关&#xff0c;结构中采用阻尼器/滚珠轴承等器件&#xff0c;使其非常具有质感和手感。功能上我构思不能只有电脑开关这么简单地一…

Jenkins CI/CD 持续集成专题三 Jenkins 使用shell脚本打包组件配置流程

第一步 新建任务 第二步 输入项目名称和选择自由风格的软件项目点击确定 第三步 配置下项目地址和账号密码 第四步 配置 build steps 选择 shell 脚本 第五步 shell 配置 &#xff08;注意shell 必须以#!/bin/sh开头&#xff0c;否则会报 找不到shell 命令的错&#xff09; …

【Web】DASCTF X CBCTF 2022九月挑战赛 题解

目录 dino3d Text Reverser cbshop zzz_again dino3d 进来是一个js小游戏 先随便玩一下&#xff0c;显示要玩够1000000分 直接console改分数会被检测 先是JSFinder扫一下&#xff0c;扫出了check.php 到js里关键词索引搜索check.php 搜索sn&#xff0c;发现传入的参数是…

3-羟基丙酸(3-HP)应用前景广阔 生物基3-羟基丙酸市场占比将不断提升

3-羟基丙酸&#xff08;3-HP&#xff09;应用前景广阔 生物基3-羟基丙酸市场占比将不断提升 合成技术是制约3-羟基丙酸规模化应用的重要因素。3-羟基丙酸合成技术包括化学合成法、生物合成法两大类&#xff0c;其中化学合成法是主流生产工艺&#xff0c;但化学合成法存在工艺复…

.netcore+vue新生分班系统的设计与实现

.netcore vue新生分班系统的设计与实现说明文档 运行前附加数据库.mdf&#xff08;或sql生成数据库&#xff09; 主要技术&#xff1a; 基于.net core架构和mysql数据库vue 东北石油大学新生分班系统的设计与实现 功能模块&#xff1a; 登录 注册学生 忘记密码 系统首顶 个…

完成学校官网页面制作

<!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <title>教务系统</title> <style> .wap{ margin:0 auto; width:955px; } .top{ height:150px; padding-left:85px; …

DHCP服务器配置故障转移后显示红色箭头、与伙伴服务器失去联系的解决方案

一、遇到的故障现象&#xff1a; &#xff08;主DHCP服务器与备用DHCP服务器连通性正常&#xff0c;在故障转移选项卡上却显示与伙伴失去联系、伙伴关闭&#xff0c;且ipv4协议旁边显示一个红色的小箭头&#xff09;&#xff0c;正常情况下是绿色 &#xff08;一&#xff09;…

外包干了6天,技术明显退步。。。

我是一名大专生&#xff0c;自19年通过校招进入湖南某软件公司以来&#xff0c;便扎根于功能测试岗位&#xff0c;一晃便是近四年的光阴。今年3月&#xff0c;我如梦初醒&#xff0c;意识到长时间待在舒适的环境中&#xff0c;已让我变得不思进取&#xff0c;技术停滞不前。更令…

金三银四面试题(二十):单例模式知多少?

设计模式也是面试中的热门考题&#xff0c;基本这个部分都是问问你知不知道XXX设计模式&#xff0c;有什么用&#xff0c;优缺点&#xff0c;然后再现场手写一个demo。很多时候是和spring一起考的&#xff0c;问问你知不知道spring框架用了哪些设计模式。今天我们来先看看单例模…

代码+视频,R语言对数据进行多重插补后回归分析

我们在临床做回顾性研究分析中经常要面对数据缺失的问题&#xff0c;如果数据缺失量大就会对我们的研究结果产生影响&#xff0c;近年来&#xff0c;对数据进行多重插补广泛应用于SCI论文中。我们在之前的文章中已经演示了使用SPSS对数据进行多重插补并分析。今天&#xff0c;我…

贪吃蛇的简单实现(c语言)

前言&#xff1a;学完了C语言的基础语法&#xff0c;和一点数据结构的知识&#xff0c;拿贪吃蛇来练练手&#xff0c;并熟悉以前的知识。写完之后&#xff0c;有一种成就感&#xff0c;为以后的学习饱满激情。 注意这里的讲解是由部分到整体的思路。 目录 控制台不能是终端&am…

ubuntu环境下使用g++把c++编译成汇编语言(暂时)

1. 引言 为了深入理解c&#xff0c;决定学习一些简单的汇编语言。使用ubuntu系统下g很容易将一个c的文件编译成汇编语言。本文使用此方法&#xff0c;对一个简单的c文件编译成汇编语言进行理解。 2.示例 文件名&#xff1a;reorder_demo.cpp #include<stdio.h>typede…

逻辑回归+分类的评估方式

一&#xff1a;什么是逻辑回归 解决二分类问题 二&#xff1a;损失及优化 三&#xff1a;逻辑回归API 四&#xff1a;案例 五&#xff1a;分类的评估方式 评估公式 分类评估API ROC与AUC&#xff08;介绍API&#xff09;衡量不平衡样本 ROC曲线的绘制 分类中解决类别不平衡

HackmyVM-----Boxing靶机

文章目录 正常打靶流程1.获取靶机IP地址2.获取靶机端口服务3.访问网页4.添加域名WindowsLinux 5.访问域名6.nc反弹shell 7.结束 正常打靶流程 1.获取靶机IP地址 ┌──(root㉿kali)-[/home/kali] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:10:3c:9b, …