基于VS调试分析 + 堆栈观察问题代码段

文章目录
- 问题代码段1 —— 阶乘之和
- 问题代码段2 —— 越界的危害
- ① 发现问题
- ② 分析问题
- ③ 思考问题【⭐堆栈原理⭐】
- ④ 解决问题【DeBug与Release】
- 👨程序员与测试人员👩
- ✒总结与提炼
问题代码段1 —— 阶乘之和
先来看一道C语言中比较基础的题目,求解阶乘的和,通过调试来观察为何会出现问题,如觉得已经会了的读者可以直接看第二道题
- 先上代码。逻辑很简答,首先输入n表示,表示n个阶乘之和,然后在内部循环中求出每一个数的阶乘,计算所得进行累加,最后便有了【阶乘之和】
int main()
{int sum = 0; //保存最终结果int n = 0;int ret = 1; //保存n的阶乘scanf("%d", &n);for (int i = 1; i <= n; i++){for (int j = 1; j <= i; j++){ret *= j;}sum += ret;}printf("%d\n", sum);return 0;
}
简单一些,计算1! + 2! + 3!的阶乘之和。
- 首先看到我们进入了内存循环,此时
i = 1, j = 1,内部的循环只会执行一次,求出1! = 1

- 此时的sum便为1

- 接着进入【2!】的计算,内部循环会执行2次,此刻
i = 2, j = 1

- 此时算出
ret = 2,即【2! = 2】,累加到sum中,此时sum == 3

- 接下去计算【3!】,内部循环会执行3次,3!应该要为6
- 此刻
i = 3, j = 1,注意观察此时的ret为2

- 当
j == 2时ret进行了一次累乘,值便为4

- 当
j == 3时继续进行累乘,此时ret为12

- 此时跳出循环开始累加【3!】的结果,此时
sum == 15,结果错误❌

相信在认真看了我步步分析之后,你一定可以清楚为什么会出错❓
- 原因就在于每次在计算下一个数的阶乘时,上一次累乘之后的ret没有进行一个重置,便导致在计算下一个阶乘的时候重复累乘

- 这样运算结果就正确了,
1! + 2! + 3! = 9

你觉得这篇文章真的就那么简单吗?那我就不会写了,上面只是热热身,下面这个才叫做【真正的问题】
问题代码段2 —— 越界的危害
① 发现问题
好,我们来看这个代码段,首先请你给出它最终的运行结果💻
- 相信很多同学都认为这段代码最后的结果是程序报错,因为一眼就看出了for循环的边界条件有问题,导致产生了
数组访问越界 - 如果你是这么想的,那我要这么告诉你:你是个正常人😀,我问了我身边的朋友,第一时间就觉得这一定是一个
越界错误,不过它的运行结果并不是你想的那样,而是一个死循环😵
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;
}

② 分析问题
看了上面的这些结果,相信很多读者都非常诧异(・∀・(・∀・(・∀・*)。究竟是为什么呢?我们一起先来分析一下🔍
- 通过调试来进行观察,首先我们进入循环,初始化数组

- 然后通过动图先将数组的合法10个元素修改为0,这里的快捷操作是
F9设置断点+F5运行到下一断点处

- 但是可以看到,循环的终止条件为
i == 12,那此刻循环就会继续执行,那你就会想arr[10]这个位置不是越界非法的嘛,为什么访问不会报错呢?

- 首先我们来讨论一下
arr[10]这个位置,为什么可以访问到? - 从下图可以看出,在内存中对于一个数组而言是连续存储的,数组后面的这些空间其实也是存在arr数组之后,它们都存在于main函数的函数栈帧中,对于函数栈帧来说,一块块存储空间都是紧密相连的,所以要想访问数组之后的空间也是可以访问到,只是在我们的意识中他确实是存在越界访问的行为
- 可能还是有同学不理解,举个简单的【例子】:假设你现在在一家酒店里,和家里人一起出来旅游,找了酒店中三个连在一起的房间住一晚上。你们旁边呢还有很多房间,都是连在一起的,那这个时候你可以闯入别人的房间🏠吗?虽然行为上是非法的,但是呢又是可以做得到的,只是会被人打一顿而已。这就可以理解为【数组的越界访问】

- 但是越界访问也就算了,难不成真的可以修改没分配内部空间的值吗,我们来看看

- 可以看到,这个位置上的值确实是发生了一个修改Σ(っ °Д °;)っ,而且没有报出错误,继续循环。其实对于编译器来说,有时候的
越界访问不报出错误是正常的,因为它有时候确实检查不到,就和交警不可能查到每一个醉酒的司机是一个道理,不然为什么有这么多车祸呢🚗 - 非但是可以修改第10个位置的元素,后面第11个它也进行了修改。虽然这已经见怪不怪了

- 可是呢,到了第12个位置的时候,却出现了奇怪的事情,这块地址上的数竟然不是一个随机值,我们都知道面对未初始化的数据都是一个无符号整型(unsigned int),上面也看到过,是一个很大的负数,可是呢这个位置上的数确是
12,而且刚好是i == 12这个边界的位置

- 此时当我再执行
arr[i] = 0时间,就发生了这样的事👇此时的【i】和【arr[12]】两个值竟然同时发生了变化

- 那有同学就更加差异了,这是为啥呀?????

③ 思考问题【⭐堆栈原理⭐】
分析了问题出现的地方,接下去就让我们通过堆栈的内存布局和原理来分析一下为何会出现这样的情况
- 刚才看到了当程序运行完
arr[12] = 0时arr[12]和【i】这两个位置的值一起发生了变化,那就会思考它们会不会是一样的呢? - 接着分别在调试的时候取出它们的地址就可以发现确实是同一块空间【我第一次看到这个结果的时候也感觉很惊奇!】

- 其实就可以想到,对于变量i,应该是位于数组结束位置的后两位位置,这样才会在越界访问数组的时候导致访问到【i】,然后在修改这块块空间中的值时将循环变量【i】的值做了修改,那也就使得【i】永远到不了13,那也就不会跳出这个循环,会一直循环下去

- 变量【i】辛辛苦苦地通过循环加到了13,眼看前面的路就要走完了,但是呢你又给他拽回了起点,那也就只好重新开始。可是呢一次也就算了,你就是和它过不去,就站在
13这个位置上,每次看【i】一到13就把他拖回起点,这也就使得它永远都过不去了 —— 血海深仇❣

当然就通过这样的方式来看还是了解不到在内存中它们究竟发生了什么,就下去我就通过画内存图的方式带你一探究竟🔍
- 内存布局呢分为【堆区】【栈区】【静态区】三大块,对于像
arr数组和变量i这些都属于局部变量,对于局部变量来说都是存放在【栈区】中的。也就是我们说过的函数栈帧它就是在栈区开辟空间的 - 栈也可以称做为【堆栈】,它的使用习惯是:先使用高地址,再使用低地址。这一点很重要,是理解的关键所在!
- 通过创建两个变量来进行观察就可以发现先创建的变量就会先创建的变量就会现在栈中为其开辟空间,因此可以看到变量a的地址是比变量b的地址来得大的

- 下面我画的这张图其实就是内存中栈区的真正模样,也就是从上往下进行生长,上面是高地址,下面是低地址。因此一进到main函数的函数栈帧中时,就会先为变量【i】开辟一块空间,接着可能就会空出几个位置再为arr数组开辟十个元素的空间
- 可是呢有同学就会疑问,为什么要空出几个位置,而不是直接紧随其后就在变量【i】后面为其分配10块空间呢?这一点我们到后面再议👇

- 我们都知道对于数组的下标来说是从低到高进行变化的,也就是
从0 ~ 9,那对于数组的地址是如何变化的呢?我们通过VS来看看

- 通过打印数组中每个元素的下标就可以发现数组中每个元素的地址是由低到高进行一个变化的。这一点也很重要,是理解的关键所在!
然后再去看上面这张图你就可以知道为什么在越界访问数组的时候会访问到先创建出来的变量【i】了
👉虽然变量【i】是先创建出来的,先开辟的空间;而数组arr是后创建出来的,后开辟的空间。不过呢,因为数组的下标和每一个元素的地址都是从低到高进行一个变化的。又因为堆栈的使用习惯是:先使用高地址,再使用低地址,所以当数组在进行向后访问的时候,就有可能找到变量【i】,就有可能把【i】覆盖掉,就有可能把这个循环变量改成意想不到的值,导致循环的结束条件永远都不会成立,永远都是真,这也就导致了死循环产生👈
你,明白了吗👀
最后的话再来解释一下为什么开辟了变量【i】的栈帧空间后要空出几个位置才为数组arr开辟空间,而且刚好是两个这么巧呢?
- 其实这不是我瞎说的,也不是我能决定的,而是取决于编译器。
- 在
VC6.0这个很老的编译器中,其实在局部变量的栈帧空间开辟中是不会再创建多余空间的; - 在
gcc这个Linux环境下的编译器中,创建的局部变量之间会空出一个整型,也就是4个字节 - 但是在
VS 2013/2019/2022这些编辑器中,中间都会空出两个整型,也就是8个字节
- 在
- 所以这段代码其实你在不同编译器下去运行虽然都是死循环,但是死循环的临界点和循环的这个范围都是不同的。例如在Linux下的gcc去编译运行的话
i = 11就会发生死循环,具体的有兴趣可以去试试
④ 解决问题【DeBug与Release】
好,上面我们通过一系列的问题排查和思索,最终发现了问题所在,那现在就来更正一下这个问题
- 其实如何更正你已经可以想到了,那就是把对于变量【i】的定义放到数组定义的后面,这样数组在进行越界访问的时候就不会访问到后面的【i】了
- 不过可以看到,终于是出现了大家一开始想到的
越界访问的情况

- 不过这种做法可是不对的,数组越界访问应该是我们要避免的一个问题,所以真正要做出修改的应该是循环中访问数组的结束条件

- 接下去我们再来看一个神奇的事情,对于代码在编译器中的运行环境我们可以知道有【DeBug】和【Release】两个版本,我将会出现死循环的这中定义方式放在两个不同的斑纹下进行了运行,查看变量i和数组的边界地址
- 然后便发现【Relsase】版本对变量i的地址做了一个
优化,使其变到了数组arr的前面,这样在数组向后进行越界访问的时候就不会发生覆盖造成同时修改了
希望在看了我上述对这个问题的讲解之后,今后在碰到类似的问题也可以照常去分析排查🔍
👨程序员与测试人员👩
说一个程序员和测试人员之间的小故事📖
- 在公司里面,有产品经理,有部门主干,有安全人员,有运维人员,也有程序员和测试人员🐶
- 但是我们都流传着对于程序员和测试人员是【仇敌】,为什么这么说呢?因为程序员只需要实现当前这段给他的业务逻辑,不需要考虑其他内容,所以他就专注于这块的实现,因此对自己的代码很有信心。可是呢人无完人,每个人都会出错,不然程序员为何要修这么多BUG呢?
- 当测试人员在他的电脑上运行开发部发来的代码时,却出现了问题,可能是栈溢出、访问异常或者是我们上面说到的访问越界,当她排查了半天发现代码逻辑有问题时,就非常气愤地找到写这块逻辑的程序员👇

此时就开始了它们的一些争吵。。。。。。。。。此处省略一万字
- 两个人就这么吵了几十分钟,然后旁边一个资深程序员🐒看不下去了,过来帮忙看了看运行测试了一下发现这块代码逻辑在【DeBug】和【Release】环境下得到的结果是不一样的。
- 这也就解开了两人之间的矛盾,一对照便知原来测试人员使用的是【Release】环境,而程序员使用的是【DeBug】环境
- 对于DeBug环境下运行代码,会保留很多的调试信息供程序员测试代码逻辑
- 对于Release环境下运行代码,会省略很多的调试信息,可能刚好省略了什么重要的内容
- 所以在工作的时候若是发现和测试人员对口的内容不一致,可以去看看运行环境是否一样
✒总结与提炼
来总结一下本文所学习的内容
- 首先我们通过一个简单的问题代码段教了大家如何去使用调用排查问题,算是进行了入门
- 接着通过一段看上去简单但是内部逻辑很是复杂的问题代码段,较大家如何去一步步地排查、分析、思考问题,通过画出堆栈的原理图,找出问题所在,继而解决问题
- 最后我们通过【程序员与测试人员】之间的故事知道了原来在DeBug和Release环境下运行的代码可能会出现不一样的结果,也多了一个和测试人员解决问题的手段😀
以上便是本文要介绍的所有内容,感谢您的观看,记得给个三连支持哦❤️❤️❤️

相关文章:
基于VS调试分析 + 堆栈观察问题代码段
文章目录问题代码段1 —— 阶乘之和问题代码段2 —— 越界的危害① 发现问题② 分析问题③ 思考问题【⭐堆栈原理⭐】④ 解决问题【DeBug与Release】👨程序员与测试人员👩✒总结与提炼问题代码段1 —— 阶乘之和 先来看一道C语言中比较基础的题目&#x…...
QFramework框架学习
主要学习内容TypeEventSystemActionKitTimer类1、TypeEventSystem-适用于一个条件触发,多个组件响应的情况例如:动物园系统中,点击肉食动物按钮,动物园中有肉食属性的动物都进行显示。步骤:1、动物自身脚本上进行判断是…...
移动OA系统,联动企业协作让办公高效无间断
移动oa系统,近年来随着企业办公节奏的变化及人们个性化办公需求的增加迎来了快速发展。一方面,它兼具OA系统诸多优势,既凝聚了企业基础管理工作,联动了企业协作、沟通交流,又进一步提高了企业的综合实力与市场竞争力。…...
结构体熟练掌握--实现通讯录
魔王的介绍:😶🌫️一名双非本科大一小白。魔王的目标:🤯努力赶上周围卷王的脚步。魔王的主页:🔥🔥🔥大魔王.🔥🔥🔥 ❤️…...
腾讯云CVM服务器购买流程手把手方法教程攻略
购买腾讯云服务器有两种方式。一种是在官方活动中,简单方便,但ECS配置相对固定;另一种是在ECS页面定制购买。配置选项丰富,但地理可用性区域、计费模式、CPU内存实例规格、映像系统、存储系统磁盘、网络带宽和安全组的选择更为复…...
九龙证券|“春季躁动”行情要来?1月新增投资者数大增
新增投资者数量在上一年12月触及多年新低后,2023年1月份开端呈现反弹。 在新增投资者数量之外,近段时刻以来,包含A股商场股票成交额、北向资金净买入额、两融资金规划及成交额在内多个商场目标也呈现回暖的特征,目前A股商场交投氛…...
C语言(按位运算符和位移运算符)
目录 编辑 一.按位运算符 1.二进制反码或按位取反:~ 2.按位与:& 3.按位或:| 4.按位异或:^ 二.位移运算符 1.左移: << 2.右移: >> 一.按位运算符 C有四个按位逻辑运算符都用于整…...
删掉的照片怎么恢复?
每一张照片都是生活,留住每一个人的回忆。而这些有意义的照片,我们都会把它保存在我们的手机或电脑上,始终伴随着我们。但无论是手机还是电脑,都是需要时不时清理一下的。如果是清理垃圾图片时,不小心删除了需要的图片…...
【java】40 个 SpringBoot 常用注解(建议收藏)
本文目录一、Spring Web MVC 注解Spring Web MVC 注解RequestMappingRequestBodyGetMappingPostMappingPutMappingDeleteMappingPatchMappingControllerAdviceResponseBodyExceptionHandlerResponseStatusPathVariableRequestParamControllerRestControllerModelAttributeCross…...
【JMC】SMILES‑based deep generative scafold decorator for de‑novo drug design
SMILES-based deep generative scaffold decorator for de-novo drug design 基于SMILES的利用Fragment的分子生成模型 https://github.com/undeadpixel/reinvent-scaffold-decorator 1.背景 深度生成模型因其可以从有限的数量中生成新数据,目前已成功应用于生成…...
全链路异步,让你的 SpringCloud 性能优化10倍+
背景 随着业务的发展,微服务应用的流量越来越大,使用到的资源也越来越多。 在微服务架构下,大量的应用都是 SpringCloud 分布式架构,这种架构,总体是全链路同步模式。 同步编程模式不仅造成了资源的极大浪费&#x…...
131.《router v 5 与 react-router v 6》
文章目录1.什么是路由2.路由分类3.react-router-dom的理解4. react-router-dom相关API5.其他6. react-router5 路由基本使用1.效果2.代码App.js一级路由home.js下的二级路由7.路由传参的三种方式8.react-router6 基本使用1.一级路由2.二级路由3.hooksuseRoutesuseParamsuseSear…...
2023第十届北京老年产业博览会/中国养老护理人才培育计划
CBIAIE北京老博会,打造2023年度唯具参展价值的老年行业盛会; 北京老博会:2011年,我国首场以“老年产业”为主题,一场专注于老年福祉、健康的国际型行业发展盛会,中国(北京)国际老年…...
STM32F407VET6 / BLACK_F407VE开发板间隔0.5秒不断重启
有一块 STM32F407VET6 的故障开发板, 之前的问题是经常无法烧录, 必须reset之后才能连接, 具体查看这篇 STM32F407VET6烧录出现flash download failed target dll has been cancelled. 并且程序运行一段时间后会halt. 这块开发板后来一直搁箱底吃灰了几年. 最近打算把这片 STM…...
什么是圈复杂度
圈复杂度是一种软件度量指标,用于度量程序中的控制流程的复杂性。它是通过计算程序中独立路径的数量来确定的。简单来说,圈复杂度是指在一个函数或模块中有多少个独立的路径,也就是说,有多少个不同的输入序列可以导致不同的执行路…...
Hbase 数据迁移
Hbase 数据迁移 可选方案对比 l 已验证方案操作说明: n Export&import u 导出命令及示例 hbase org.apache.hadoop.hbase.mapreduce.Export “表名” 文件路径 导出至本地文件系统: ./bin/hbase org.apache.hadoop.hbase.mapreduce.Export ‘defa…...
Docker consul的容器服务更新与发现
一、Consul概述(1)什么是服务注册与发现服务注册与发现是微服务架构中不可或缺的重要组件。起初服务都是单节点的,不保障高可用性,也不考虑服务的压力承载,服务之间调用单纯的通过接口访问。直到后来出现了多个节点的分…...
数据库关系模型
关系模型简述 形象地说,一个关系就是一个table。 关系模型就是处理table的,它由三个部分组成: 描述DB各种数据的基本结构形式;描述table与table之间所可能发生的各种操作;描述这些操作所应遵循的约束条件࿱…...
你是真的“C”——详解指针知识
你是真的“C”——详解指针知识😎前言🙌1、 指针是什么?🙌2、指针和指针类型🙌2 、1指针-整数2 、 2指针的解引用3、 野指针🙌3、 1野指针成因3、 2如何规避野指针4、指针运算🙌4、1 指针-整数4…...
React/ReactNative面试攻略(偏RN)
useMemo Vs useCallBackuseMemo第一个参数返回的是值,useCallBack返回的是函数useMemo和useCallBack第二个参数都是依赖项useMemo避免组件非依赖项更新时参数的计算useCallback避免父组件非依赖项更新时造成子组件的重复渲染React.memo 使用场景纯prue组件ÿ…...
docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...


