问题

我有以下代码.

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

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

代码只是运行时没有运行时异常!

输出为 58

怎么可能?是不是一个局部变量的内存不可访问的函数之外?



解决方法

How can it be? Isn't the memory of a local variable inaccessible outside its function?

您租一间酒店房间.你把一本书放在床头柜的顶部抽屉里,去睡觉.你第二天早上退房,但"忘记"给你的钥匙.你偷了钥匙!

一个星期后,您将返回酒店,不要办理入住手续,使用您的被盗钥匙偷进您的旧房间,然后在抽屉里查看.你的书还在那里.惊人!

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

很明显,这种情况可能发生在现实世界没有问题.没有神秘的力量,导致你的书消失,当你不再被授权在房间里.也没有一个神秘的力量,阻止你进入一个房间与被盗的钥匙.

不需要 要移除您的书籍.你没有与他们签订合同,说如果你留下东西,他们会为你撕碎.如果您使用被盗钥匙非法重新进入您的房间以取回钥匙,则酒店安全人员不需要 来捕捉您的偷盗行为.您没有与他们签订合同,说"如果我试着偷偷回到我的房间后,你需要阻止我.相反,你与他们签订了一份合同,说"我保证不会后来偷偷回到我的房间,"一个合同,你突破了.

在这种情况下,任何事情都可能发生.这本书可以在那里 - 你有幸运.别人的书可以在那里,你的可以在酒店的炉子.当你进来时,有人可能在那里,撕毁你的书.酒店可以把桌子和书完全取代了一个衣柜.整个酒店可能即将被拆除,替换为一个足球场,你会在爆炸时死亡,而你偷偷摸摸.

你不知道会发生什么;当您签出酒店并窃取了以后非法使用的钥匙时,您放弃了生活在可预测,安全的世界中的权利,因为您选择违反该系统的规则.

C ++不是安全的语言.它会高兴地允许你打破系统的规则.如果你试图做一些非法和愚蠢的事情,回到一个房间,你没有被授权进入,通过一个桌子,甚至可能不再有,通过C ++不会阻止你.比C ++更安全的语言通过限制你的力量来解决这个问题 - 例如通过对键进行更严格的控制.

UPDATE

圣洁的好处,这个答案得到了很多的关注. (我不知道为什么 - 我认为它只是一个”有趣”的小比喻,但无论如何.)

我认为这可能是更新这一点有几个技术性的想法.

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

第一种是有某种"长寿命"存储区,其中存储器中每个字节的"生存期" - 也就是说,它与某些程序变量有效关联的时间段 - 不能是容易预先提前预测.编译器生成对"堆管理器"的调用,知道如何在需要时动态分配存储,并在不再需要时回收.

第二种是具有某种”短期”存储区域,其中存储中的每个字节的生存期是众所周知的,并且特别地,存储器的寿命遵循”嵌套”模式.也就是说,最长寿命的短期变量的分配与其后的短期变量的分配严格重叠.

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

因为这个原因,局部变量通常生成为”堆栈”数据结构上的存储,因为堆栈有一个属性,推送它的第一个东西将是最后一个弹出.

这就像酒店决定只租房间顺序,你不能退房,直到房间号码比你高的所有人都签出.

那么让我们考虑一下堆栈.在许多操作系统中,每个线程获得一个堆栈,并且堆栈被分配为一定的固定大小.当你调用一个方法,东西被推到堆栈.如果你然后传递一个指向堆栈的指针返回你的方法,正如原始海报在这里,这只是一个指向一些完全有效的百万字节内存块的中间的指针.在我们的比喻,你退房的酒店;当你做,你刚刚检出最高编号的占用房间.如果没有其他人在您之后签到,并且您非法回到您的房间,您的所有资料都会保证在此特定酒店 .

我们使用堆栈为临时商店,因为他们真的便宜和容易. C ++的实现不需要使用堆栈来存储本地;它可以使用堆.它不,因为这将使程序更慢.

C ++的实现不需要留下你留在堆栈上的垃圾没有触摸,所以你可以回来为它后来非法;它是完全合法的编译器生成的代码,回到零所有的"房间",你刚刚腾出的零.它不会再次,这将是昂贵的.

不需要C ++的实现,以确保当堆栈逻辑收缩时,曾经有效的地址仍然映射到内存中.该实现允许告诉操作系统"我们已经完成使用这个堆栈的页面,直到我说,否则,发出一个异常,如果任何人触及以前有效的堆栈页面,破坏该过程.再次,实现并不实际这样做,因为它是缓慢和不必要的.

相反,实现会让你犯错误,逃避它.大多数时候.直到有一天真正可怕的东西出问题,过程爆炸.

这有问题.有很多规则,很容易意外打破它们.我当然有很多次.更糟的是,这个问题通常只是表明,当内存检测到损坏数十亿纳秒的腐败发生后,当很难弄清楚谁搞砸了.

更多记忆安全的语言通过限制您的力量解决这个问题.在"正常"C#中,简单地没有办法获取本地的地址并返回它或存储它以供以后使用.你可以取一个本地的地址,但语言是巧妙的设计,使它是不可能使用它在本地端的生命周期.为了获取本地地址并将其传回,您必须将编译器置于特殊的"不安全"模式下,在程序中加入"不安全"字样,以引起注意事实上,你可能在做一些危险的,可能是违反规则.

进一步阅读:




相关问题推荐