局部变量的内存是否可以在其范围之外访问?

我有以下代码。

int * foo()
{
    int a = 5;
    return &a;
}

int main()
{
    int* p = foo();
    cout << *p;
    *p = 8;
    cout << *p;
}

代码只是在没有运行时异常的情况下运行!

产量是58

怎么会这样? 在其功能之外,是不是本地变量的内存不可访问?


怎么会这样? 在其功能之外,是不是本地变量的内存不可访问?

你租一个旅馆房间。 你把书放在床头柜的最上面的抽屉里,然后去睡觉。 你在第二天早上退房,但“忘记”放弃你的钥匙。 你偷了钥匙!

一个星期后,你回到酒店,不要登记入住,用偷来的钥匙偷偷摸进你的旧房间,然后看着抽屉。 你的书还在那里。 惊人!

怎么可能? 如果您没有租用房间,是不是酒店房间抽屉的内容不可用?

那么,显然这种情况在现实世界中可能发生没有问题。 当你不再被授权进入房间时,没有任何神秘的力量会导致你的书消失。 也没有一种神秘的力量可以阻止你进入一间带有被盗钥匙的房间。

酒店管理部门不需要删除您的图书。 你没有和他们签订合同,说如果你把东西留在后面,他们会为你撕碎。 如果你用偷来的钥匙非法重新进入你的房间把它拿回来,酒店保安人员不需要让你偷偷溜进去。你没有和他们签订一份合同,说“如果我试图偷偷回到我的身边之后的房间,你必须阻止我。“ 相反,你与他们签订了一份合同,上面写着“我保证不会在以后偷偷回到我的房间里”,这是你违约的合同。

在这种情况下会发生任何事情 。 这本书可以在那里 - 你很幸运。 别人的书可以在那里,你的可以在酒店的炉子里。 当你进来时,有人会在那里,把你的书撕成碎片。 酒店可能已经拆下桌子并完全预订,并将其换成衣柜。 整个酒店可能即将被拆除,换成一个足球场,而当你偷偷摸摸时,你将在爆炸中死亡。

你不知道会发生什么, 当您离开酒店偷走了一把钥匙后将其非法使用时,您放弃了生活在一个可预测,安全的世界中的权利,因为您选择违反系统规则。

C ++不是一种安全的语言 。 它会高兴地让你打破系统的规则。 如果你试图做一些非法和愚蠢的事情,比如回到一个你无权进入的房间,并通过一个可能不在那里的桌子翻找,C ++不会阻止你。 比C ++更安全的语言通过限制你的能力来解决这个问题 - 例如,通过对密钥进行更严格的控制。

UPDATE

圣善,这个答案引起了很多关注。 (我不知道为什么 - 我认为这只是一个“有趣”的小类比,但无论如何。)

我认为用更多的技术思想来更新这一点可能是密切相关的。

编译器负责生成用于管理该程序操纵的数据存储的代码。 有很多不同的方式来生成代码来管理内存,但随着时间的推移,两种基本技术已经成为根深蒂固的。

首先是要有某种“长寿命”的存储区域,其中存储器中每个字节的“生命周期” - 也就是与某些程序变量有效关联的时间段 - 无法轻易预测的时间。 编译器将调用生成为“堆管理器”,它知道如何在需要时动态分配存储,并在不再需要时将其回收。

第二种是拥有某种“短暂”存储区域,其中存储器中每个字节的生命周期是众所周知的,特别是存储的生命周期遵循“嵌套”模式。 也就是说,寿命最长的短期变量的分配严格覆盖了其后的较短寿命变量的分配。

局部变量遵循后一种模式; 当输入一个方法时,它的局部变量就会生效。 当该方法调用另一个方法时,新方法的局部变量就会生效。 在第一个方法的局部变量死亡之前,它们将会死亡。 可以提前计算与局部变量相关的存储的生命周期的开始和结束的相对顺序。

出于这个原因,局部变量通常是作为“堆栈”数据结构中的存储而生成的,因为堆栈的属性是第一件事就是弹出最后一个东西。

这就像酒店决定只按顺序租用房间一样,只有房间号码超过您的每个人都已签出,才能退房。

所以我们来考虑一下堆栈。 在许多操作系统中,每个线程会得到一个堆栈,并且堆栈被分配为某个固定的大小。 当你调用一个方法时,东西被压入堆栈。 如果你将一个指向堆栈的指针传回你的方法,就像原始的海报在这里做的那样,这只是指向一个完全有效的百万字节内存块中间的指针。 在我们的比喻中,你退房的酒店; 当你这样做时,你只是检查出了编号最高的房间。 如果没有其他人在您之后登记入住,并且您非法返回您的房间,那么您所有的东西都会保证在这家特定的酒店中继续存在。

我们使用临时商店的堆栈,因为它们非常便宜且容易。 C ++的实现不需要使用堆栈来存储本地人; 它可以使用堆。 它不会,因为这会使程序变慢。

C ++的实现并不需要将你留在堆栈上的垃圾保持原样,以便以后可以非法返回; 编译器生成的代码将您刚刚腾出的“房间”中的所有内容都归零,这是完全合法的。 这不是因为再次,这将是昂贵的。

不需要C ++的实现来确保堆栈逻辑缩小时,过去有效的地址仍然映射到内存中。 该实现允许告诉操作系统“我们现在已经使用这个页面的堆栈了,直到我不这么说的时候,如果有人触及之前有效的堆栈页面,就会发出一个破坏进程的异常”。 同样,实现实际上并不这样做,因为它很慢且不必要。

相反,实现可以让你犯错误并逃脱。 大多数时候。 直到有一天真正糟糕的事情出现了问题,这个过程爆炸了。

这是有问题的。 有很多规则,并且很容易意外打破它们。 我当然有很多次。 更糟糕的是,这个问题通常只在腐败发生后检测到内存腐化数十亿纳秒时才会出现,因为很难弄清楚是谁搞砸了。

更多的内存安全语言通过限制你的能力来解决这个问题。 在“正常”的C#中,根本无法取得本地地址,并将其返回或存储以备后用。 你可以把一个地方的地址,但语言的设计巧妙,以便在本地生效后不可能使用它。 为了获取本地地址并将其传回,必须将编译器置于特殊的“不安全”模式,并在程序中放置“不安全”一词,以唤起您注意到您可能正在执行的事实有可能违反规则的危险。

为进一步阅读:

  • 如果C#确实允许返回引用呢? 巧合的是,这是今天的博客文章的主题:

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

  • 为什么我们使用堆栈来管理内存? C#中的值类型是否总是存储在堆栈中? 虚拟内存是如何工作的? 以及C#内存管理器如何工作的更多主题。 这些文章中的很多与C ++程序员密切相关:

    https://blogs.msdn.microsoft.com/ericlippert/tag/memory-management/


  • 你在做什么在这里仅仅是读取和写入内存曾经是的地址a 。 现在你已经超出了foo ,它只是一个随机存储区域的指针。 在你的例子中恰好如此,那个存储区域确实存在,此刻没有其他的东西在使用它。 你不会因为继续使用而破坏任何东西,也没有其他东西会覆盖它。 因此, 5仍然存在。 在一个真正的程序中,这个记忆几乎会立即被重复使用,并且你会因为这样做而破坏某些东西(尽管这些症状可能在很晚之后才会出现!)

    当您从foo返回时,您告诉操作系统您不再使用该内存,并且可以将其重新分配给其他内容。 如果你很幸运,并且它永远不会被重新分配,并且操作系统不会让你再次使用它,那么你就会摆脱谎言。 虽然你最终可能会写下任何与该地址相关的结果。

    现在,如果你想知道为什么编译器不会抱怨,那可能是因为foo被优化消除了。 它通常会警告你这类事情。 ç假设你知道你在做什么,虽然,在技术上还没有在这里侵犯范围(有没有提到a本身之外的foo ),只读存储器访问规则,仅触发警告而不是错误。

    简而言之:这通常不会奏效,但有时会偶然。


    因为存储空间尚未被踩踏。 不要指望这种行为。

    链接地址: http://www.djcxy.com/p/6185.html

    上一篇: Can a local variable's memory be accessed outside its scope?

    下一篇: Breaking out of nested loops