这个问题是知乎上的一个问题,看了以后觉得比较有意思。代码短到只有十多行,但是这么短的代码却输出了很奇怪的结果。很多人回答的时候都是站在理论的角度上说明代码的问题,但是实际的问题还是没有说明其中的问题。

问题是“C 语言局部变量,堆与栈的问题?”

问题的地址如下:https://www.zhihu.com/question/60415017

知乎上的问题

以上就是知乎中的问题,基本上把问题也描述清楚了,对于它的问题看似诡异,其实并不复杂。这个问题涉及几个知识点,第一是关于内存分配的问题,第二是关于函数调用时栈帧的开辟与回收的问题。当然了,如果是纯理论的描述问题,其实只会把问题越搞越糊涂,如果结合调试器问题就不同了。

以下是我在知乎的回答(因为当时回答时随意了一些,所以这里再简单的整理了一下,从分割线开始,就是我整理过的回答了)。


遇到类似的问题,通过在调试器中进行单步调试,然后再观察其反汇编代码,一般就知道其中的问题所在了。

先来了解几个简单的概念性的问题:

首先,局部变量保存在栈中;

其次,new 分配的空间在堆中。

栈空间是由 ESP 和 EBP 寻址(x86架构的平台下),这两个寄存器是由 CPU 控制维护的。ebp 作为栈帧的基址来说,函数调用完后会自动恢复到被调用之前,那么栈中的数据其实还是存在的。esp 作为栈顶指针,在函数返回后,也会被收回。虽然栈帧在函数返回后被回收,但是其中的数据并没有被回收,因此之前的数据仍然是存在的。很多书上说,访问这样的地址会给出随机值,其实不是,只是这些值我们不再确定是什么值而已,但是它不是随机的。

new 出来的堆空间,如果不 delete 是不会释放的,也就是说 new 完以后的地址只要不释放,在其他代码中都可以使用。

以上就是 堆 空间和 栈 空间的简单描述。

上面是理论部分,下面实际观察一下。

我用的环境是 VS2012,和提问者的环境不同,但是过程是相同的。

看一下 func 函数的反汇编代码,这里我用的 DEBUG 方式编译的。

在 func 函数的 return 处下断点,然后运行到此处,观察其反汇编代码,并打开寄存器窗口监视窗口内存窗口。

看下面的截图:

变量的地址是 0x0103fd6c,而 i 的值是0x0132a670,这值是一个地址,也就是由 new 分配的堆地址,看一下 0x0132a670 这个地址中的值,如下图:

而 0x0103fd6c 是变量 i 的地址,这个地址在栈中,如下图:

上面的寄存器的值是在 func 函数中的值,看一下 ebp 和 esp 的值。

返回 main 函数,如下图:

上图是返回 main 函数后的寄存器的值。

再看 0x0132a670 地址中内存的值仍然没变……

这就是堆的效果,即 new 的情况。

这部分内存如果不是人为去写,一般数据不会被修改或覆盖。

前面说的是数组在堆中的情况,如果是在栈中的话,那么数组 i 的值都在栈中,即7、9、5 也在栈中。

简单说一下。

仍然在 func 的 return 处下断点,运行到这里,观察:

此时在 func 函数内,继续单步返回到 main 函数内:

观察,现在 ESP 和 EBP 已经恢复到 main 函数的栈帧内,而且代码也运行到了 main 的 for 内。

但是内存的栈中,func 函数内的 i 数组仍然存在。虽然栈帧被回收,但是数据仍在,通常情况是无法访问它们的,但是现在把 i 的地址返回给 main 函数,因此还是可以访问到它的。

发现执行到完 call 以后,栈中的数据被破坏了,因为用的是单步步过,其实只要进入 call 以后,原来栈中的数据就被破坏了。

那么为什么 7 能被正确的输出呢?因为在栈还没破坏之前,7 已经当作 printf 的参数被送入栈中当作参数了。看那句 push edx 即可。

剩下的输出就不说了,反正栈已经被破坏了。剩下的就理所当然有问题了。


以上就是我给出问题的答复,其实整个过程还算简单。记得我在学习的时候,我的老师说过这么一句话,“学编程不看内存,相当于游泳不下水”。当然了,也许并不是每门编程语言都有机会去观察其运行时的内存情况,但是,了解如何调试还是非常有趣的事情,因为很多看似不好解释的问题,其实在调试器下面都是可以看到问题本质的。


我的微信公众号:“码农UP2U”

一个有趣的C语言问题的更多相关文章

  1. 12个有趣的C语言问答(详解)

    本文参照博文<12个有趣的C语言问答>,在原文的基础上增加来对应的知识点的详细介绍. 1 gets()方法 Q:下面的代码有一个被隐藏的问题,你能找到它吗? #include <st ...

  2. 《12个有趣的C语言问答》(4)

    C语言面试问答——<12个有趣的C语言问答>评析(4) 前文链接:http://www.cnblogs.com/pmer/p/3324063.html 8,Making changes i ...

  3. 《12个有趣的C语言问答》评析2

    <12个有趣的C语言问答>评析(2) 前文链接:http://www.cnblogs.com/pmer/p/3313913.html (没存盘,遭遇过热保护.至少4个问答的评论白写了.默哀 ...

  4. 12个有趣的c语言面试题&nbsp;

    1.gets()函数 问:请找出下面代码里的问题: #include int main(void) { char buff[10]; memset(buff,0,sizeof(buff)); gets ...

  5. 一个有趣的js隐式转换的问题

    一个有趣的js隐式转换的问题 在chrome的控制台中打印一下表达式 [] + {} //结果为 [object object] 然后调整顺序打印 {} + [] //结果为 0 然后将两个表达式组合 ...

  6. 举一个有趣的例子,让你轻松搞懂JVM内存管理

    目录 前言 例子 源码 输出 图解 深入分析 学以致用 写在最后 前言 在JAVA虚拟机内存管理中,堆.栈.方法区.常量池等概念经常被提到,对理论知识的理解也常常停留在字面意思上,比如说堆内存中存放对 ...

  7. 【小贴士】关于transitionEnd/animate的一个有趣故事

    前言 在很久之前,我们项目有一个动画功能,功能本身很简单,便是典型的右进左出,并且带动画功能 以当时来说,虽然很简单,但是受限于框架本身的难度,就直接使用了CSS3的方式完成了功能 当时主要使用tra ...

  8. 分享:写了一个 java 调用 C语言 开发的动态库的范例

    分享:写了一个 java 调用 C语言 开发的动态库的范例 cfunction.h   代码#pragma once#ifdef __cplusplusextern "C" {#e ...

  9. 一个有趣的SQL Server 层级汇总数据问题

        看SQL Server大V宋大侠的博客文章,发现了一个有趣的sql server层级汇总数据问题.          具体的问题如下:     parent_id emp_id emp_nam ...

随机推荐

  1. C# 栈的应用

    栈的特性:后进先出(LIFO) 回文判断 类似123321,123a321即为回文 思路: 将字符串前一半入栈 依次弹出栈与字符串后一半比较 public static bool IsPlalindr ...

  2. Flink 编程接口

    欢迎来 kk大数据,今天分享的是 Flink 提供了哪些编程接口可以给我们开发. 一.数据集类型 现实世界中,所有的数据都是以流式的形态产生的,不管是哪里产生的数据,在产生的过程中都是一条条地生成,最 ...

  3. 50 (OC)* URL Scheme 网页地址协议

    在Xcode 9 下,新建的工程,在plist文件中注册URL Schemes,从safari无法打开问题 1:URL Scheme是什么 2:URL Scheme有什么作用 3:URL Scheme ...

  4. SpringBoot区块链之以太坊区块高度扫描(简洁版)

    继续昨天的demo往下写写:[SpringBoot区块链之以太坊开发(整合Web3j)](https://juejin.im/post/5d88e6c1518825094f69e887),将复杂的逻辑 ...

  5. BOM之定时器

    JavaScript中的时间是通过定时器控制的,他们分别是window.setInterval和window.setTimeout,我们当然可以省略window,直接使用方法名称调用. 一     s ...

  6. 【linux】【jenkins】自动化运维七 整合sonarqube代码审查

    1.安装插件:SonarQube Scanner for Jenkins 插件安装教程参考:https://www.cnblogs.com/jxd283465/p/11542680.html 2.So ...

  7. WebApi简介

    简单创建.NET Core WebApi:https://www.cnblogs.com/yanbigfeg/p/9197375.html 登陆验证四种方式:https://www.cnblogs.c ...

  8. centos7下mongoDB安装和配置

    2018-10-31更新 yum –y install mongodb-org 找不到这个包,清华源: https://mirrors.tuna.tsinghua.edu.cn/help/mongod ...

  9. Sentinel Core流程分析

     上次介绍了Sentinel的基本概念,并在文章的最后介绍了基本的用法.这次将对用法中的主要流程和实现做说明,该部分主要涉及到源码中的sentinel-core模块. 1.token获取   如上为t ...

  10. Hibernate教程 ---简单易懂

    1 web内容回顾 (1)javaee三层结构 (2)mvc思想 2 hibernate概述 3 hibernate入门案例 4 hibernate配置文件 5 hibernate的api使用 Hib ...