本文由 伯乐在线 - 菜鸟浮出水 翻译自 StackOverflow。欢迎加入 技术翻译小组。转载请参见文章末尾处的要求。

问题:Can a local variable’s memory be accessed outside its scope? 有一段局部变量的内存,可以从其范围之外访问它么?

如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
int *foo()
{
    int a = 5;
    return &a;
}
int main()
{
    int *p = foo();
    cout << *p;
    *p = 8;
    cout << *p;
}

这样的代码可以正常执行,而且没有任何运行时的异常!

输出是 5 8

这是怎么回事?难道局部变量在函数外也可以被访问吗?

来自微软资深软件工程师 Eric Lippert 的最佳答案(3200+赞):

你在酒店里租了一间房。你把一本书放进了桌子的第一个抽屉里,然后就去睡觉了。当你第二天早上醒来时,你假装忘记去还钥匙了。你偷了房间的钥匙!

一周之后,你回到了酒店,但没有入住,你用偷来的钥匙溜进了你上次入住的房间,并查看了那个抽屉。你的书还在那里。是不是很令人吃惊!

这是怎么回事呢?难道一个酒店房间的抽屉不是应该无法被一个没有入住这个房间的人看到吗?

好吧,明显的是,这种情况在真实世界中当然会发生。在你不入住这个房间的时候,这里面没有任何神秘的力量把你的书弄消失掉,也没有魔法能够阻止你用偷来的钥匙进入房间。

酒店的管理规章里没有要求拿走你的书。你也没有跟他们说如果你落下了一本书,他们可以帮你撕毁它。如果你用偷来的钥匙非法进入了你上次的房间,并且没有被酒店的安保系统发现。你也没有跟他们说如果你之后尝试溜进房间,他们应该阻止你。不过事实上,你确实签了一份协议规定你保证不会偷偷溜回房间。只不过你打破了协议。

在这种情况下任何事情都有可能发生。如果你运气好的话,那本书可能还在那里。其他人的书也可能在那个抽屉里而你的书则被丢进了酒店的火炉里。也可能当你溜进去的时候正好有个人在把你的书撕成碎片。酒店可能把那张桌子连带你的书都移走了,而把一个衣柜放在那里。这家酒店也可能正好要被拆除,换成一个足球场,在你溜来溜去的时候。你可能会在一场爆破中死去。

当你离开酒店而偷了房间的钥匙的时候,你不知道将会发生什么。你放弃了去生活在一个可靠的,安全的世界里,因为你选择去打破系统的规则。

C++不是一门安全的语言。你可以非常轻松就打破这个系统的规则。如果你尝试去做一些非法并且愚蠢的事情,比如你回到那个你已经不入住的房间,并想要去查看那张也许已经不存在的桌子。C++不会阻止你的。比C++更加安全的语言通过限制你的能力来解决这个问题,比如通过更加严格的控制房间钥匙。

【更新】:

我的老天。这个答案获得了这么多的关注。(我不知道为什么,我只是觉得这样比喻比较有趣, 不过管他呢。)

我认为在经过了更加技术性的思考之后更新一下这个答案是必要的。

编译器的工作是生成代码来管理这个程序数据拥有的内存。有很多方式来生成管理内存的代码,但是这么多年来有两个基本的技术是必须要知道的。

第一个是拥有一片长期存在的区域,这片存储区域里的每一个字节,他们的生命周期比较长。生命周期的意思就是它们能够被程序访问的时期。这类内存没办法提前进行预估。编译器生成一种叫堆管理器的代码,它知道如何在需要的时候动态的分配内存,当内存不再被需要的时候释放掉他们。

第二个是拥有一片短期存在的区域,这片存储区域里的每一个字节都可以提前进行预估。而且比较特殊的是,这片区域的生命周期遵循一种嵌套模式。也就是说,在这片区域中拥有最长生命周期的变量,它所分配的内存地址被它之后分配的那些生命周期较短的变量所重用。

局部变量就是第二种情况。当调用一个函数时,它的局部变量便被生成了。当这个函数调用另外一个函数时,新函数的局部变量也被生成了。这些变量会在第一个函数的局部变量之前被释放掉。这些局部变量的内存地址的开始和结束可以提前被计算出来。

因为这个原因,局部变量经常被分配到栈数据结构里,因为一个栈的特点是第一个入栈的元素将会最后一个出栈。

这就好像酒店决定只能按照顺序进行房间的出租。你没办法离开,除非你之前所有房间号比你大的人都走了。

所以,让我们来想一下栈的操作过程。在很多操作系统中,每一个线程都有一个栈,并且栈的大小是一个可变的确定大小。当你调用一个函数的时候,相关的内容被压入栈内。当你把一个这个栈的指针传出这个函数时,就像上面的提问者所干的一样。那个指针只是指向全部有效的数百万个字节内存块的中间。在我们的类比中,当你离开酒店的时候,你只是离开了当前被占用的数字最大的房间。如果没有人在你之后入住,你又非法地回到了这个房间。你所有的东西肯定都还在这个酒店的房间里。

我们用栈作为临时存储因为它们非常廉价并且容易实现。C++的实现没有规定一定要用栈来存储局部变量,你可以使用堆来存储它们,不过没有人这么干,因为那样做会使得程序变得很慢。

C++也没有规定在你离开栈之后需要清掉栈里的内容,所以你可以在之后非法地回到栈里找到你之前的内容。当然编译器如果生成代码,一旦你不再使用了就把栈里的所有内容都清零,这是完全合法的。不需要再解释为什么了,因为这样做代价非常高。

C++没有规定要确保当栈变小时,之前有效的内存地址依然有效。C++的实现也允许告诉操作系统“我们已经不再需要栈的个内存页了。除非我说,否则当有任何人要访问这个之前有效的栈的内存页的时候抛出一个异常并结束程序”。再次,一般的实现也没有这么做,因为这么说使程序变慢而且没有必要。

相反,大多数时候,一般的C++实现允许你犯错然后避免它。直到有一天,一些真正非常令人恐怖的错误出现了然后把整个程序弄崩溃了。

这样做是有问题的。C++里有如此多的规则而又如此轻易就可以打破它们。我自己就有好多次这样的经历。更糟的是,这种问题往往是表面的,当你发现内存地址冲突了之后去检查内存,却发现它们在很长时间内又是正确的。所以你很难知道到底是哪个地方出错了。

那些内存安全的语言通过限制你的能力来解决这个问题。在规范的C#里,没有任何办法去获取一个局部变量的内存地址,然后返回它或者是存储它等以后再用。你可以获取一个局部变量的内存地址,但是语言被很好的设计了,你不可能在局部变量生命周期之后还能够使用它。为了取得局部变量的内存地址并把它返回,你必须要把编译器设置为一个特殊的不安全的模式,并且在你的程序里写上“unsafe”关键字。这可以帮助提醒你,你正在做一些不安全的可能会打破规则的事情。

更进一步阅读:
当C#返回引用时做了些什么?

http://blogs.msdn.com/b/ericlippert/archive/2011/06/23/ref-returns-and-ref-locals.aspx

为什么我们用栈来管理内存?C#里值的类型是否一直存储在栈里?虚拟内存是如何工作的?以及更多的关于C#内存管理是如何工作的。这里许多文章都对C++程序员有帮助。

http://blogs.msdn.com/b/ericlippert/archive/tags/memory+management/

酒店房间和 C++ 局部变量的作用域相关推荐

  1. 认清JavaScript和JAVA全局变量和局部变量的作用域

    认清JavaScript和JAVA全局变量和局部变量的作用域 2010年2月28日 george 发表评论 阅读评论 通过淘宝面试题来认清JavaScript和JAVA全局变量和局部变量的作用域 注意 ...

  2. C语言中全局变量和局部变量,作用域与生命周期的相关问题。

    C语言中变量分为局部变量和全局变量: 在程序的开头定义的是全局变量,在函数内部定义的是局部变量. 在这里涉及2个概念,作用域与生命周期. 作用域指的是描述变量在哪段代码中有效:生命周期指的是变量什么时 ...

  3. C语言基础1:初识C语言(转义、注释;数组、操作符、反码、补码、static、define、指针、结构体常变量;局部变量;作用域、生命周期)

    文章目录 C语言基础1:初识C语言 1.C语言简介 1.1什么是C语言 1.2C语言的发展 2.第一个C语言程序 2.1创建项目 2.2添加源文件 2.3写代码 3.数据类型 4.变量.常量 4.1定 ...

  4. 记报错 error: jump to case label [-fpermissive] 局部变量的作用域问题

    文章目录 ❯ cd "f:\myCPlusPlusCode\DataStructure\Stack\" ; if ($?) { g++ -fexec-charset=GBK bra ...

  5. Effective Java~57. 将局部变量的作用域最小化

    优先选择 for 循环而不是 while 循环 例如,下面是遍历集合的首选方式 // Preferred idiom for iterating over a collection or array ...

  6. 第57条:将局部变量的作用域最小化

    在代码块的开头就声明局部变量不是个好习惯,要改 local variable应该在第一次使用的地方声明,过早的声明会分散阅读程序人的注意力(这个变量干嘛用的?) ⭐️ 局部变量声明时就应该进行&quo ...

  7. 静态、全局和局部变量在作用域和内存空间的详解

    1.从作用域看: C++变量根据定义的位置的不同的生命周期,具有不同的作用域,作用域可分为6种: 全局作用域,局部作用域,语句作用域,类作用域,命名空间作用域和文件作用域. 1>全局变量具有全局 ...

  8. 静态局部变量的作用域

    静态局部变量的生存期虽然为整个源程序,但是其作用域仍与自动变量相同,即只能在定义该变量的函数内使用该变量,退出该函数后,尽管该变量还继续存在,但不能使用它. 静态变量与之相对应的是在运行时只暂时存在的 ...

  9. day10 局部变量 全局变量 作用域前奏

    规则命名以及基本介绍 1 name="LHF" # 顶头写的全局都可以调用的就是全局变量,命名规则要求大写全局变量 2 def chang_name(): 3 # global n ...

最新文章

  1. python classmothed_python函数之classmethod()
  2. maven+svn+hudson+weblogic构建持续集成环境
  3. 在.NET Core类库中使用EF Core迁移数据库到SQL Server
  4. MODIS NDVI数据处理相关问题
  5. 承包你所有壁纸需求,高图网图片,美到窒息
  6. Python——如何屏蔽函数内部的print输出
  7. BZOJ 4802 欧拉函数(Pollard_Rho)
  8. 自动化运维落实到位的三点基础及常用工具对比
  9. 计算机更新bios,win7bios升级教程_win7电脑主板bios升级的方法
  10. ant-desigh+prolayout布局,修改面包屑的分隔符号,百度搜遍了都找不到办法,csdn解决了
  11. 【武汉加油!中国加油!】挑战七天 实现机器视觉检测有没有戴口罩系统——第四五六七天
  12. 关于SpringSecurity登陆成功出现302状态码
  13. ie自带css定位,CSS实现元素相对于浏览器窗口进行定位_css
  14. 计算机策略更新失败用户策略更新失败,升级更新win10遇到错误代码8024000B的两种解决办法...
  15. php国际青少年书画大赛,【水墨童心】第五届 国际青少年书画大赛征稿通知
  16. 位运算 位运算应用
  17. appium原理及api
  18. ORACLE学习笔记(6)(分析函数)
  19. js的for循环中return可以跳出循环,而使用forEach等不可以跳出循环
  20. python矩阵的切片操作

热门文章

  1. spectral安装
  2. 【转】1.5 运行原理剖析
  3. Sharepoint学习笔记—架构系列
  4. angular设置referer_Angular-cli 构建应用的一些配置
  5. Qt事件传递相关问题
  6. 【Python 必会技巧】使用 zip() 函数对序列进行压缩和解压操作
  7. PWN-PRACTICE-BUUCTF-5
  8. ☆用Python实现ACM各大输入输出格式
  9. 【POJ - 3320 】Jessica's Reading Problem (尺取,哈希)
  10. 【HDU - 1452】 Happy 2004(因子和,逆元,快速幂)