实用调试技巧【下篇】
🔴本文章是在 Visual Studio 2022(VS2022)编译环境下进行操作讲解
文章目录
- 3.2.调试的时候查看程序当前信息
- 3.2.1.查看临时变量的值
- 3.2.2.查看内存信息
- 3.2.3.查看调用堆栈
- 3.2.4.查看汇编信息
- 🥳4.调试实例
- 🥳5.如何写出(易于调试)的代码
- 5.1.模拟实现库函数: strcpy
- 5.2. const 修饰指针
- 🥳6.编程常见的错误

3.2.调试的时候查看程序当前信息
3.2.1.查看临时变量的值
🔴自动窗口
👉调试–>窗口–>自动窗口


它会出现一个自动窗口,它会自动捕获变量的值,让你监视,方便的我们不用手动添加变量,但是如果它不想让你看的时候过了一会就会自动取消了,我们就监视不到了
🔴局部变量
👉在自动窗口的下面

与自动窗口比较相似,但不同的是放的是局部变量,监视的是程序执行中的局部变量
🔴监视
👉调试–>窗口–>监视

这才是我们用到最多的调试功能

我们想监视谁,就输入谁,它不会自己取消,会一直显示,来为我们提供对变量的监视


数组也可以观察,变量的地址也都可以观察到
如果有需要,可以同时打开4个监视窗口,每个窗口都可以拖动,放到你想放的位置
3.2.2.查看内存信息
🔴内存
👉调试–>窗口–>内存


☝️内存中本放的是二进制的数据,但是为了展示方便,所以是以十六进制来显示的☝️
3.2.3.查看调用堆栈
🔴调用堆栈
👉调试–>窗口–>调用堆栈

👇简单知道一下 什么是栈👇

👇看这段代码👇

👇调用堆栈👇







☝️反映的是函数的调用逻辑☝️
3.2.4.查看汇编信息
🔴反汇编
👉在内存的下面

👇看到的是c语言代码翻译出来的汇编代码👇

🥳4.调试实例
👇看下面这段代码👇
#include<stdio.h>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;
}
运行起来是什么样呢?

数组已经越界了,为什么没有崩溃而是死循环了呢?
👇进入调试👇

i 等于9的时候一切都正常,我们继续👇

这个代码真的是在越界访问!胆大包天啊,如果我们继续下一步呢👇

继续一直执行

再改

i 到12之后并没有++变成13, i 再一直跟着 arr[12] 改变,陷入了死循环,这是为什么呢?有没有可能它们是在同一个空间呢?我们看一下👇

地址居然一摸一样,它俩在一个空间😕
那么底层原理什么呢?为什么会这样呢?👇
🔴原理:
1. i 和 arr 是局部变量,局部变量是放在栈区上的
2. 栈区内存的使用习惯是 使用高地址的空间,再使用低地址处的空间
3. 数组随着下标的增长,地址是由低到高变化的

🥳5.如何写出(易于调试)的代码
🥰优秀的代码:
🙌 1. 代码运行正常
🙌 2. bug很少
🙌 3. 效率高
🙌 4. 可读性高
🙌 5. 可维护性高
🙌 6. 注释清晰
🙌 7. 文档齐全
🥰常见的coding技巧:
🙌 1. 使用assert
🙌 2.尽量使用const
🙌 3. 养成良好的编码风格
🙌 4. 添加必要的注释
🙌 5. 避免编码的陷阱
5.1.模拟实现库函数: strcpy

拷贝一个字符串
示例👇
#include<stdio.h>
#include<string.h>int main()
{char arr1[] = "hello world";char arr2[20] = { 0 };strcpy(arr2, arr1);printf("%s\n", arr2);return 0;
}

👇模拟实现 strcpy👇
#include<stdio.h>
#include<string.h>
void my_strcpy(char* dest, char* src)
{while (*src != 0){*dest = *src;dest++;src++;}*dest = *src; // \0的拷贝
}
int main()
{char arr1[] = "hello world";char arr2[20] = { 0 };my_strcpy(arr2, arr1);printf("%s\n", arr2);return 0;
}
如果写成这样的代码,这算一个好的代码吗?当然不算一个好的代码,我们可以优化一下👇
void my_strcpy(char* dest, char* src)
{while (*src != 0){*dest++ = *src++;}*dest = *src; // \0的拷贝
}
我们还可以再融合一下👇
void my_strcpy(char* dest, char* src)
{while (*dest++ = *src++){;}
}
可以直接一次搞定
这样改进完好像已经足够好了,但是我们还能再改进👇
void my_strcpy(char* dest, char* src)
{if (dest == NULL || src == NULL){return;}while (*dest++ = *src++){;}
}
防止遇到空指针,但是这样处理遇到问题只是回避掉了,并不做处理👇
#include<stdio.h>
#include<string.h>
#include<assert.h>
void my_strcpy(char* dest, char* src)
{//断言assert(dest != NULL);assert(src != NULL);while (*dest++ = *src++){;}
}
int main()
{char arr1[] = "hello world";char arr2[20] = { 0 };my_strcpy(arr2, NULL);printf("%s\n", arr2);return 0;
}

使用断言处理之后,如果有问题它就会报错,把问题抛出来并且告诉你出错在哪里,如果不使用断言执行程序的话代码就崩掉了,不会告诉你哪里有问题👇

🚨断言是对程序员非常友好的东西,我们使用断言是个很好的编程习惯
🚨使用断言别忘记引用头文件 <assert.h>
void my_strcpy(char* dest, char* src)
{//断言assert(dest && src);while (*dest++ = *src++){;}
}
简便一点可以直接写成这样☝️
5.2. const 修饰指针

const修饰变量m之后,m的值就更改不了了
但是可以使用一些小聪明把m改掉👇
int main()
{const int m = 10;//m = 20;//errorint* p = &m;*p = 20;printf("%d\n", *p);return 0;
}

这个代码非常奇葩,因为 const 只是在语法层面限制了m不能改,但是通过地址是可以更改的
我们可以在语法层面把指针p 也进行限制👇

int main(){int n = 100;const int m = 10;//m = 20;//error//const 修饰指针//1. const放在*的左边,*p不能改了,也就是p指向的内容,不能通过p来改变了。但是p是可以改变的,p可以指向其他变量const int* p = &m;p = &n;printf("%d\n", *p);return 0;}

int main(){int n = 100;const int m = 10;//m = 20;//error//const 修饰指针//2. const放在*的右边,限制的是p,p不能改变,但是p指向的内容*p,是可以通过p来改变的int* const p = &m;*p = n;printf("%d\n", *p);return 0;}

🤜 1. const放在的左边,p不能改了,也就是p指向的内容,不能通过p来改变了。但是p是可以改变的,p可以指向其他变量
🤜2. const放在的右边,限制的是p,p不能改变,但是p指向的内容p,是可以通过p来改变的
void my_strcpy(char* dest,const char* src)
{//断言assert(dest && src);while (*dest++ = *src++){;}
}
所以上面模拟实现strcpy的代码这样写就更严谨了,加上了 const 来修饰char* src
🥰提高了代码的健壮性(鲁棒性)
因为如果下面的 *dest++ = *src++ 位置要是不小心写反了程序就会报错,所以这样写才是有意义的!
我们再来进行最后一次优化👇
#include<stdio.h>
#include<string.h>
#include<assert.h>//strcpy函数返回的是目标空间的起始位置
char* my_strcpy(char* dest,const char* src)
{//断言-- 保证指针的有效性assert(dest && src);char* ret = dest;//把src指向的字符串拷贝到dest指向的数组空间,包括\0字符while (*dest++ = *src++){;}return ret;
}
int main()
{char arr1[] = "hello world";char arr2[20] = { 0 };//链式访问printf("%s\n", my_strcpy(arr2, arr1));return 0;
}
🥰这些都是一些技巧,希望大家可以理解🥰
🥳6.编程常见的错误
🥰优秀的代码:
🔴1. 编译型错误
👉直接看错误提示信息 解决问题,或者凭借经验就可以搞定,相对简单
🔴2. 链接型错误
👉看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不存在或者拼写错误(ctrl + F 可以进行搜索)
🔴3. 运行时错误
👉借助调试,逐步定位问题,最难搞
🚨做个用心的人!积累排错经验!
总结🥰
本文章是在 Visual Studio 2022(VS2022)编译环境下进行操作讲解
以上就是调试技巧下篇内容啦🥳🥳🥳🥳
希望我们可以做一个用心的人💕💕💕
小的会继续学习,继续努力带来更好的作品😊😊😊
创作写文不易,还多请各位大佬uu们多多支持哦🥰🥰🥰
相关文章:
实用调试技巧【下篇】
🔴本文章是在 Visual Studio 2022(VS2022)编译环境下进行操作讲解 文章目录3.2.调试的时候查看程序当前信息3.2.1.查看临时变量的值3.2.2.查看内存信息3.2.3.查看调用堆栈3.2.4.查看汇编信息🥳4.调试实例🥳5.如何写出&…...
【数据结构期末例题】
前言 本文是博主自己在准备学校数据结构考试时的总结,各个知识点都贴有对应的详细讲解文章以供大家参考;当然文中还有许许多多的截图,这些是博主对主要内容的摘取,对于那些基础较好的同学可以直接看截图,减少跳转对应文…...
管理物理和快照备数据库(Physical and Snapshot Standby Databases)
1.打开物理备数据库 物理备数据库可以打开做只读访问,用于从主数据库卸载查询负载。 如果已经购买Oracle Active Data Guard选项的授权,当数据库打开时Redo Apply可以是激活的,因此允许查询返回与从主数据库返回的完全相同的结果…...
双目立体视觉:SAD算法
算法原理SAD(Sum of absolute differences)是一种图像匹配算法。基本思想:差的绝对值之和。此算法常用于图像块匹配,将每个像素对应数值之差的绝对值求和,据此评估两个图像块的相似度。该算法快速、但并不精确,通常用于多级处理的…...
海外问卷调查答题技巧,纯干货分享,新手小白看过来
海外问卷调查为什么别人赚得盆满钵满而我却连通过都不行?是不是经常有人发出这种疑问,东哥作为一个结交过很多做问卷调查行业的跨境人士,也了解到很多做这一行的去答题的时候都是掌握一定技巧的,而不是去乱答。今天东哥就来说说国…...
【NGINX入门指北】Nginx Web 架构实验
Nginx Web 架构实验 文章目录Nginx Web 架构实验一、动态网站结构二、LNMP 动态网站环境部署三、fastcgi & php-fpm:四、php-fpm初始化配置五、Nginx Location、六、Nginx Rewrite七、CA&HTTPS八、Nginx 的平滑升级一、动态网站结构 资源 资源文件识别——…...
rtt-nano移植
nano其他功能移植 添加finsh组件打开宏实现rt_hw_console_getchar函数添加finsh组件到工程总结问题1. 移植到stm32G0过程中出现Undefined symbol rt_hw_interrupt_disable (referred from clock.o)??2. “implict declaration of function ‘ ‘ is invalid in c99??3. 关于…...
cnn+transformer
好的,下面是使用 Transformer 加 CNN 实现语义分割的代码,使用的数据集是 Semantic Segmentation Drone Dataset。 首先,我们需要导入必要的 Python 库和模块。我们将使用 PyTorch 深度学习框架来实现模型: #python import torch import torch.nn as nn import torch.nn.fu…...
Python fileinput模块:逐行读取多个文件
前面章节中,我们学会了使用 open() 和 read()(或者 readline()、readlines() )组合,来读取单个文件中的数据。但在某些场景中,可能需要读取多个文件的数据,这种情况下,再使用这个组合࿰…...
Vue3路由传参
vue3路由和vue2差别不是很大,不过在传参形式上略有改变 在Vue3中使用路由必须引入 useRouter 和 useRoute import { useRoute, useRouter } from vue-routerconst Router useRouter() //跳转const Route useRoute() //获取到值 同Vue2一样,query使用p…...
用户管理——认证功能JWT和Session
目录用户认证功能的技术选型JWT和Session的区别基于JWT和Session的认证流程基于JWT的认证流程基于Session的认证流程基于JWT和Session的认证的优缺点基于JWT和Session的认证的安全性基于JWT和Session的认证的性能分析基于JWT的一次性和无法废弃基于JWT和Session的认证的续签选择…...
hashlib — 加密哈希算法
hashlib — 加密哈希算法 1.概述 加密可以保护消息的安全,以便验证它们的准确性并且使它们受保护不被拦截。 Python 的加密方式支持包括利用像 MD5 和 SHA 这样的标准算法对消息内容产生签名的 hashlib 和验证消息没有在传输过程中被改变的 hmac hashlib 哈希库模…...
四喜临门选股预警源码指标
{四喜临门选股预警} AP1:CROSS(MA(C,5),MA(C,10)); RSV:(CLOSE-LLV(LOW,9))/(HHV(HIGH,9)-LLV(LOW,9))*100; K:SMA(RSV,3,1); D:SMA(K,3,1); AP2:CROSS(K,D); DIFF:EMA(CLOSE,12) - EMA(CLOSE,26); DEA:EMA(DIFF,9); AP3:CROSS(DIFF,DEA); AP4:CROSS(MA(V,5),MA(V,10)); GYTJ1:…...
Kotlin新手教程五(扩展)
一、扩展 在Kotlin中可以给一个类添加一个新的方法而不用继承该类或者使用设计模式,这样的方法称为扩展。 1.扩展函数 声明一个扩展函数,我们需要用一个 接收者类型 也就是被扩展的类型来作为他的前缀。 下面代码为 MutableList 添加一个swap 函数&am…...
QT入门Containers之Widget、Frame
目录 一、QWidget界面相关 1、布局介绍 2、基本界面属性 3、特殊属性 二、QFrame 三、Demo展示 此文为作者原创,创作不易,转载请标明出处! 一、QWidget界面相关 1、布局介绍 为什么将QWidget容器放在第一个,因为目前使用过…...
数据结构与算法基础-学习-12-线性表之顺序队
一、个人理解队列是线性表的衍生之一,具有先进先出的特性,在队尾进行插入操作,在队头进行删除操作。队列的存储结构分为两个大类,一种是顺序队,就是用数组实现。另一种就是链队,使用链表实现。顺序队存在真…...
Python 字典(Dictionary)小窍门
字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值 key:value 对用冒号 : 分割,每个键值对之间用逗号 , 分割,整个字典包括在花括号 {} 中 ,格式如下所示:d {key1 : value1, key2 : value2 }注意:dict …...
知识图谱构建技术综述
摘要 *知识图谱为实现语义化智能搜索以及知识互联打下了基础,。, *随着知识的发展,传统的基于模板和规则构建的知识图谱已经被深度学习所替代。 知识组织得原则中:知识的充分性、有序性和标准化规则。深度学习的效果在很大程度上…...
环境变量和进程地址空间
目录 环境变量: env:显示所有的环境变量: echo $环境变量名表示查看环境变量的值 理解环境变量: getenv:显示环境变量的值 export set命令:显示所有变量 unset取消变量: pwd:当…...
【数据结构】栈和队列
目录 一、栈 1、栈的定义 2、栈的模拟实现(顺序栈) 1、创建一个顺序结构的栈 2、实现压栈方法(push) 3、模拟实现pop方法(出栈) 4、模拟实现peek(查看) 5、测试上述方法 3、栈的应用场景 1、改变元…...
大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...
3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
HarmonyOS运动开发:如何用mpchart绘制运动配速图表
##鸿蒙核心技术##运动开发##Sensor Service Kit(传感器服务)# 前言 在运动类应用中,运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据,如配速、距离、卡路里消耗等,用户可以更清晰…...
MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...
前端高频面试题2:浏览器/计算机网络
本专栏相关链接 前端高频面试题1:HTML/CSS 前端高频面试题2:浏览器/计算机网络 前端高频面试题3:JavaScript 1.什么是强缓存、协商缓存? 强缓存: 当浏览器请求资源时,首先检查本地缓存是否命中。如果命…...
