可以在其作用域之外访问局部变量的内存吗? [英] Can a local variable's memory be accessed outside its scope?

查看:23
本文介绍了可以在其作用域之外访问局部变量的内存吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下代码.

#include <iostream>

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

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

而且代码正在运行,没有运行时异常!

And the code is just running with no runtime exceptions!

输出为58

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

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

推荐答案

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

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

您租了一间酒店房间.你把一本书放在床头柜最上面的抽屉里然后睡觉.您第二天早上退房,但忘记"归还您的钥匙.你偷了钥匙!

You rent a hotel room. You put a book in the top drawer of the bedside table and go to sleep. You check out the next morning, but "forget" to give back your key. You steal the key!

一周后,你回到酒店,没有登记入住,拿着偷来的钥匙潜入你的旧房间,然后在抽屉里翻找.你的书还在.惊人!

A week later, you return to the hotel, do not check in, sneak into your old room with your stolen key, and look in the drawer. Your book is still there. Astonishing!

怎么会这样?如果你没有租过房间,酒店房间抽屉里的东西是不是就拿不到了?

嗯,很明显,这种情况可以在现实世界中发生,没问题.当您不再被授权进入房间时,没有什么神秘的力量会导致您的书消失.也没有一种神秘的力量阻止你用偷来的钥匙进入房间.

Well, obviously that scenario can happen in the real world no problem. There is no mysterious force that causes your book to disappear when you are no longer authorized to be in the room. Nor is there a mysterious force that prevents you from entering a room with a stolen key.

酒店管理人员无需要求即可移除您的图书.你没有和他们签订合同,说如果你留下东西,他们会为你撕碎.如果您使用偷来的钥匙非法重新进入您的房间以取回钥匙,酒店保安人员无需要求来抓捕您偷偷溜进来.您没有与他们签订合同说如果稍后我试着偷偷溜回我的房间,你必须阻止我."相反,您与他们签订了一份合同,上面写着我保证以后不会偷偷溜回我的房间",但您违反了.

The hotel management is not required to remove your book. You didn't make a contract with them that said that if you leave stuff behind, they'll shred it for you. If you illegally re-enter your room with a stolen key to get it back, the hotel security staff is not required to catch you sneaking in. You didn't make a contract with them that said "if I try to sneak back into my room later, you are required to stop me." Rather, you signed a contract with them that said "I promise not to sneak back into my room later", a contract which you broke.

在这种情况下任何事情都可能发生.这本书可以在那里——你很幸运.别人的书可能在那里,而你的可能在酒店的炉子里.当你进来的时候,有人可能就在那里,把你的书撕成碎片.酒店本可以完全拆除桌子和预订,并用衣柜取而代之.整个酒店可能即将被拆除并取而代之的是一个足球场,而你在偷偷摸摸的时候会死于爆炸.

In this situation anything can happen. The book can be there -- you got lucky. Someone else's book can be there and yours could be in the hotel's furnace. Someone could be there right when you come in, tearing your book to pieces. The hotel could have removed the table and book entirely and replaced it with a wardrobe. The entire hotel could be just about to be torn down and replaced with a football stadium, and you are going to die in an explosion while you are sneaking around.

你不知道会发生什么;当您退房并偷了一把钥匙以供日后非法使用时,您就放弃了生活在一个可预测的安全世界中的权利,因为选择了违反系统规则.

You don't know what is going to happen; when you checked out of the hotel and stole a key to illegally use later, you gave up the right to live in a predictable, safe world because you chose to break the rules of the system.

C++ 不是一种安全的语言.它将愉快地让您打破系统规则.如果你试图做一些非法和愚蠢的事情,比如回到一个你没有被授权进入的房间并翻找一张可能不再存在的桌子,C++ 不会阻止你.比 C++ 更安全的语言通过限制你的能力来解决这个问题——例如,通过对键进行更严格的控制.

C++ is not a safe language. It will cheerfully allow you to break the rules of the system. If you try to do something illegal and foolish like going back into a room you're not authorized to be in and rummaging through a desk that might not even be there anymore, C++ is not going to stop you. Safer languages than C++ solve this problem by restricting your power -- by having much stricter control over keys, for example.

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

Holy goodness, this answer is getting a lot of attention. (I'm not sure why -- I considered it to be just a "fun" little analogy, but whatever.)

我认为用一些更多的技术思想来更新它可能是密切相关的.

I thought it might be germane to update this a bit with a few more technical thoughts.

编译器负责生成代码,这些代码管理由该程序操作的数据的存储.生成代码来管理内存的方法有很多种,但随着时间的推移,两种基本技术已经变得根深蒂固.

Compilers are in the business of generating code which manages the storage of the data manipulated by that program. There are lots of different ways of generating code to manage memory, but over time two basic techniques have become entrenched.

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

The first is to have some sort of "long lived" storage area where the "lifetime" of each byte in the storage -- that is, the period of time when it is validly associated with some program variable -- cannot be easily predicted ahead of time. The compiler generates calls into a "heap manager" that knows how to dynamically allocate storage when it is needed and reclaim it when it is no longer needed.

第二种方法是有一个短暂"的存储区域,每个字节的生命周期都是众所周知的.在这里,生命周期遵循嵌套"模式.这些短期变量中寿命最长的将在任何其他短期变量之前分配,并最后释放.寿命较短的变量将在寿命最长的变量之后分配,并在它们之前释放.这些生命周期较短的变量的生命周期嵌套"在生命周期较长的变量的生命周期内.

The second method is to have a "short-lived" storage area where the lifetime of each byte is well known. Here, the lifetimes follow a "nesting" pattern. The longest-lived of these short-lived variables will be allocated before any other short-lived variables, and will be freed last. Shorter-lived variables will be allocated after the longest-lived ones, and will be freed before them. The lifetime of these shorter-lived variables is "nested" within the lifetime of longer-lived ones.

局部变量遵循后一种模式;当进入一个方法时,它的局部变量就会活跃起来.当该方法调用另一个方法时,新方法的局部变量就会活跃起来.他们会在第一个方法的局部变量死之前就死了.可以提前计算出与局部变量相关联的存储的生命周期开始和结束的相对顺序.

Local variables follow the latter pattern; when a method is entered, its local variables come alive. When that method calls another method, the new method's local variables come alive. They'll be dead before the first method's local variables are dead. The relative order of the beginnings and endings of lifetimes of storages associated with local variables can be worked out ahead of time.

出于这个原因,局部变量通常作为存储在堆栈"数据结构上生成,因为堆栈具有这样的特性,即第一个压入它的东西将是最后一个弹出的东西.

For this reason, local variables are usually generated as storage on a "stack" data structure, because a stack has the property that the first thing pushed on it is going to be the last thing popped off.

这就像酒店决定只按顺序出租房间,直到所有房间号高于您的人都退房后,您才能退房.

It's like the hotel decides to only rent out rooms sequentially, and you can't check out until everyone with a room number higher than you has checked out.

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

So let's think about the stack. In many operating systems you get one stack per thread and the stack is allocated to be a certain fixed size. When you call a method, stuff is pushed onto the stack. If you then pass a pointer to the stack back out of your method, as the original poster does here, that's just a pointer to the middle of some entirely valid million-byte memory block. In our analogy, you check out of the hotel; when you do, you just checked out of the highest-numbered occupied room. If no one else checks in after you, and you go back to your room illegally, all your stuff is guaranteed to still be there in this particular hotel.

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

We use stacks for temporary stores because they are really cheap and easy. An implementation of C++ is not required to use a stack for storage of locals; it could use the heap. It doesn't, because that would make the program slower.

C++ 的实现不需要将您留在堆栈中的垃圾保持原样,以便您以后可以非法返回它;编译器生成的代码将您刚刚腾出的房间"中的所有内容都归零是完全合法的.不会,因为那会很贵.

An implementation of C++ is not required to leave the garbage you left on the stack untouched so that you can come back for it later illegally; it is perfectly legal for the compiler to generate code that turns back to zero everything in the "room" that you just vacated. It doesn't because again, that would be expensive.

C++ 的实现不需要确保当堆栈逻辑收缩时,曾经有效的地址仍然映射到内存中.允许实现告诉操作系统我们现在已经使用完这个堆栈页面.除非我另有说明,否则如果有人触摸以前有效的堆栈页面,则发出一个破坏进程的异常".同样,实现实际上并没有这样做,因为它很慢而且没有必要.

An implementation of C++ is not required to ensure that when the stack logically shrinks, the addresses that used to be valid are still mapped into memory. The implementation is allowed to tell the operating system "we're done using this page of stack now. Until I say otherwise, issue an exception that destroys the process if anyone touches the previously-valid stack page". Again, implementations do not actually do that because it is slow and unnecessary.

相反,实现会让你犯错并侥幸逃脱.大多数时候.直到有一天,真正可怕的事情出了问题,整个过程爆炸了.

Instead, implementations let you make mistakes and get away with it. Most of the time. Until one day something truly awful goes wrong and the process explodes.

这是有问题的.有很多规则,很容易不小心打破它们.我当然有很多次.更糟糕的是,这个问题通常只在内存在损坏发生后数十亿纳秒被检测到损坏时才会浮出水面,此时很难弄清楚是谁搞砸的.

This is problematic. There are a lot of rules and it is very easy to break them accidentally. I certainly have many times. And worse, the problem often only surfaces when memory is detected to be corrupt billions of nanoseconds after the corruption happened, when it is very hard to figure out who messed it up.

更多的内存安全语言通过限制你的能力来解决这个问题.在普通"C# 中,根本无法获取本地地址并将其返回或存储以备后用.您可以获取本地的地址,但是该语言设计得很巧妙,因此在本地结束后无法使用它.为了获取本地地址并将其传回,您必须将编译器置于特殊的不安全"模式,并且在您的程序中放置不安全"一词,以引起注意事实上,您可能正在做一些可能违反规则的危险事情.

More memory-safe languages solve this problem by restricting your power. In "normal" C# there simply is no way to take the address of a local and return it or store it for later. You can take the address of a local, but the language is cleverly designed so that it is impossible to use it after the lifetime of the local ends. In order to take the address of a local and pass it back, you have to put the compiler in a special "unsafe" mode, and put the word "unsafe" in your program, to call attention to the fact that you are probably doing something dangerous that could be breaking the rules.

进一步阅读:

  • 如果 C# 允许返回引用怎么办?巧合的是,这正是今天博文的主题:

  • What if C# did allow returning references? Coincidentally that is the subject of today's blog post:

https://ericlippert.com/2011/06/23/ref-returns-and-ref-locals/

为什么要使用栈来管理内存?C# 中的值类型总是存储在堆栈中吗?虚拟内存是如何工作的?以及有关 C# 内存管理器如何工作的更多主题.其中许多文章也与 C++ 程序员密切相关:

Why do we use stacks to manage memory? Are value types in C# always stored on the stack? How does virtual memory work? And many more topics in how the C# memory manager works. Many of these articles are also germane to C++ programmers:

https://ericlippert.com/tag/memory-management/

这篇关于可以在其作用域之外访问局部变量的内存吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆