局部变量的内存是否可以在其作用域之外访问?

I have the following code.

#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!

The output was 58

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

转载于:https://stackoverflow.com/questions/6441218/can-a-local-variables-memory-be-accessed-outside-its-scope

csdnceshi69
YaoRaoLov So the great answer should somehow stand without a question? Or do you mean it should have instead be written on another domain.com?
3 年多之前 回复
csdnceshi69
YaoRaoLov Re "you can't get away with that sort of thing these days", Why not?
3 年多之前 回复
csdnceshi55
~Onlooker can it cause segmentation fault ?
大约 4 年之前 回复
weixin_41568196
撒拉嘿哟木头 this line was added after, i just forgot to write "EDIT" and there is a result -2 on a question. When i started this question i didnt know that this question was asked already. But when i found it i edited question with that i found a dublicate and instantly repotred my question. So i dont undertand why would you minus me.
9 年多之前 回复
weixin_41568196
撒拉嘿哟木头 i mean it is not overwritten after i exit a function foo. And i can output it even if the local variable were destroyed.
9 年多之前 回复
csdnceshi75
衫裤跑路 So memory is overwritten. Otherwise you would get '55'
9 年多之前 回复
csdnceshi53
Lotus@ there is nothing impossible, besides traveling faster than speed of light (and it's not absolutely certain) :)
9 年多之前 回复
csdnceshi53
Lotus@ zero page on 8086 (and 0000:0000 too) has its usages - interrupt vectors, etc, so addressing it was quite normal. Back in the day viruses (and anti-viruses) used to overwrite quite a bit of.
9 年多之前 回复
csdnceshi53
Lotus@ Weird question w/ so much love, and I used to think that C developers MUST understand the how hardware works, the stack allocation has been the same forever.
9 年多之前 回复
weixin_41568174
from.. But this way people don't manually have to click the forward link... so it may have been a good idea. But still the merge was backwards. Trying to justify by saying it was the right way around won't work.
9 年多之前 回复
weixin_41568174
from.. dupe means (quote) "This question covers exactly the same ground as earlier questions on this topic;", not "This question covers exactly the same ground as a newer question on this topic;". Either your merge or the "close" popup has it backwards.
9 年多之前 回复
csdnceshi63
elliott.david If the answer here is good, it should be merged into older questions, of which this is a dupe, not the other way around. And this question is indeed a dupe of the other questions proposed here and then some (even though some of the proposed are a better fit than others). Note that I think Eric's answer is good. (In fact, I flagged this question for merging the answers into one of the older questions in order to salvage the older questions.)
9 年多之前 回复
csdnceshi79
python小菜 Dupe answer doesn't mean dupe question. A lot of the dupe questions that people proposed here are completely different questions that happen to refer to the same underlying symptom... but the questioner has know way of knowing that so they should remain open. I closed an older dupe and merged it into this question which should stay open because it has a very good answer.
9 年多之前 回复
weixin_41568174
from.. please provide a dupe link and I'm happy to vote for close. We can ask a moderator to merge with the question that this one is a dupe of.
9 年多之前 回复
csdnceshi60
℡Wang Yan lol. I needed to read the question and some answers before I even understood where the problem is. Is that actually a question about variable's access scope? You don't even use 'a' outside your function. And that is all there is to it. Throwing around some memory references is a totally different topic from variable scope.
9 年多之前 回复
csdnceshi72
谁还没个明天 - I think you misunderstood me... I know it is unsafe, thats for sure! I thought it would be impossible. I guess i should get used to the freedom that C++ gives the developer..
9 年多之前 回复
csdnceshi54
hurriedly% Lippert: reminds me of this: j00ru.vexillium.org/?p=781
9 年多之前 回复
csdnceshi72
谁还没个明天 - Not everyone is super wizards in C++ as you probably. Some of us still have a few things to learn, especially when comming to C++ from "safer" languages like C# and java. So unless you are really intented to teach me something here, please save me your comments.
9 年多之前 回复
csdnceshi70
笑故挽风 Bekkers: "that's because most OS-es these days have a write-protected zero-page however not all of them do!" Yea. I know.
9 年多之前 回复
csdnceshi57
perhaps? Ah man this makes me miss my C++ / DCOM / VB days. We had a home-grown red-black tree that had invalid pointer access issues. I had the distinct pleasure of debugging it.
9 年多之前 回复
weixin_41568127
?yb? Back in my youth I once worked on some kinda tricky zero-ring code that ran on the Netware operating system that involved cleverly moving around the stack pointer in a way not exactly sanctioned by the operating system. I'd know when I'd made a mistake because often the stack would end up overlapping the screen memory and I could just watch the bytes get written right onto the display. You can't get away with that sort of thing these days.
9 年多之前 回复
csdnceshi77
狐狸.fox that's because most OS-es these days have a write-protected zero-page however not all of them do!
9 年多之前 回复
csdnceshi70
笑故挽风 In some platforms/compilers (especially old compilers for DOS) you can even write through NULL pointer and everything seems OK until you overwrite something important (like the code being executed). :)
9 年多之前 回复
csdnceshi58
Didn"t forge this won't even compile as is; if you fix the nonforming business, gcc will still warn address of local variable ‘a’ returned; valgrind shows Invalid write of size 4 [...] Address 0xbefd7114 is just below the stack ptr
9 年多之前 回复

19个回答

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!

How can that be? Aren't the contents of a hotel room drawer inaccessible if you haven't rented the room?

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++ 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.

UPDATE

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.

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.

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.

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.

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.

For further reading:

What you're doing here is simply reading and writing to memory that used to be the address of a. Now that you're outside of foo, it's just a pointer to some random memory area. It just so happens that in your example, that memory area does exist and nothing else is using it at the moment. You don't break anything by continuing to use it, and nothing else has overwritten it yet. Therefore, the 5 is still there. In a real program, that memory would be re-used almost immediately and you'd break something by doing this (though the symptoms may not appear until much later!)

When you return from foo, you tell the OS that you're no longer using that memory and it can be reassigned to something else. If you're lucky and it never does get reassigned, and the OS doesn't catch you using it again, then you'll get away with the lie. Chances are though you'll end up writing over whatever else ends up with that address.

Now if you're wondering why the compiler doesn't complain, it's probably because foo got eliminated by optimization. It usually will warn you about this sort of thing. C assumes you know what you're doing though, and technically you haven't violated scope here (there's no reference to a itself outside of foo), only memory access rules, which only triggers a warning rather than an error.

In short: this won't usually work, but sometimes will by chance.

Because the storage space wasn't stomped on just yet. Don't count on that behavior.

csdnceshi70
笑故挽风 Haha. Francis Bacon, one of Britain's greatest essayists, whom some people suspect wrote Shakespeare's plays, because they can't accept that a grammar school kid from the country, son of a glover, could be a genius. Such is the English class system. Jesus said, 'I am the Truth'. oregonstate.edu/instruct/phl302/texts/bacon/bacon_essays.html
5 年多之前 回复
csdnceshi80
胖鸭 I could have sworn that I wrote that long ago, but it popped up recently and found my response wasn't there. Now I have to go figure out your allusions above as I expect I'll be amused when I do >.<
5 年多之前 回复
csdnceshi70
笑故挽风 Man, that was the longest wait for a comment since, "What is truth? said jesting Pilate." Maybe it was a Gideon's Bible in that hotel drawer. And what happened to them, anyway? Notice they are no longer present, in London at least. I guess that under the Equalities legislation, you would need a library of religious tracts.
5 年多之前 回复
csdnceshi70
笑故挽风 I like Eric's answer a lot, obviously, and I'm not just being egregious for the sake of it, but this answer has the virtue of brevity and correctness.
8 年多之前 回复

Your problem has nothing to do with scope. In the code you show, the function main does not see the names in the function foo, so you can't access a in foo directly with this name outside foo.

The problem you are having is why the program doesn't signal an error when referencing illegal memory. This is because C++ standards does not specify a very clear boundary between illegal memory and legal memory. Referencing something in popped out stack sometimes causes error and sometimes not. It depends. Don't count on this behavior. Assume it will always result in error when you program, but assume it will never signal error when you debug.

weixin_41568174
from.. Kjörling: Sure! People like to do some dirty work once in a while ;)
9 年多之前 回复
csdnceshi50
三生石@ I recall from an old copy of Turbo C Programming for the IBM, which I used to play around with some way back when, how directly manipulating the graphics memory, and the layout of the IBM's text mode video memory, was described in great detail. Of course then, the system that the code ran on clearly defined what writing to those addresses meant, so as long as you didn't worry about portability to other systems, everything was fine. IIRC, pointers to void were a common theme in that book.
9 年多之前 回复

In typical compiler implementations, you can think of the code as "print out the value of the memory block with adress that used to be occupied by a". Also, if you add a new function invocation to a function that constains a local int it's a good chance that the value of a (or the memory address that a used to point to) changes. This happens because the stack will be overwritten with a new frame containing different data.

However, this is undefined behaviour and you should not rely on it to work!

csdnceshi62
csdnceshi62 For example, while the Standard may not require that implementations allow a pointer passed to realloc to be compared against the return value, nor allow pointers to addresses within the old block to be adjusted to point to the new one, some implementations do so, and code which exploits such a feature may be more efficient than code which has to avoid any action--even comparisons--involving pointers to the allocation that was given to realloc.
2 年多之前 回复
csdnceshi62
csdnceshi62 While the storage was occupied by a, the pointer held the address of a. Although the Standard does not require that implementations define the behavior of addresses after the lifetime of their target has ended, it also recognizes that on some platforms UB is processed in a documented manner characteristic of the environment. While the address of a local variable won't generally be of much use after it has gone out of scope, some other kinds of addresses may still be meaningful after the lifetime of their respective targets.
2 年多之前 回复
csdnceshi70
笑故挽风 "print out the value of the memory block with address that used to be occupied by a" isn't quite right. This makes it sound like his code has some well-defined meaning, which is not the case. You are right that this is probably how most compilers would implement it, though.
9 年多之前 回复

A little addition to all the answers:

if you do something like that:

#include<stdio.h>
#include <stdlib.h>
int * foo(){
    int a = 5;
    return &a;
}
void boo(){
    int a = 7;

}
int main(){
    int * p = foo();
    boo();
    printf("%d\n",*p);
}

the output probably will be: 7

That is because after returning from foo() the stack is freed and then reused by boo(). If you deassemble the executable you will see it clearly.

csdnceshi55
~Onlooker it's almost a year old, but no, "function stacks" are not separated from each other. A CONTEXT has a stack. That context uses its stack to enter main, then descends into foo(), exists, then descends into boo(). Foo() and Boo() both enter with the stack pointer at the same location. This isn't however, behavior that should be relied upon. Other 'stuff' (like interrupts, or the OS) can use the stack between the call of boo() and foo(), modifying it's contents...
3 年多之前 回复
csdnceshi67
bug^君 why and how boo reuses the foo stack ? aren't function stacks separated from each other, also I get garbage running this code on Visual Studio 2015
大约 4 年之前 回复
csdnceshi72
谁还没个明天 It is called undefined behavior!
5 年多之前 回复
csdnceshi66
必承其重 | 欲带皇冠 -1 "for will probably be 7". The compiler might enregister a in boo. It might remove it because it's unnecessary. There is a good chance that *p will not be 5, but that doesn't mean that there is any particularly good reason why it will probably be 7.
大约 7 年之前 回复
csdnceshi50
三生石@ Simple, but great example to understand the underlying stack theory.Just one test addition, declaring "int a = 5;" in foo() as "static int a = 5;" can be used to understand the scope and life time of a static variable.
7 年多之前 回复

You are just returning a memory address, it's allowed but probably an error.

Yes if you try to dereference that memory address you will have undefined behavior.

int * ref () {

 int tmp = 100;
 return &tmp;
}

int main () {

 int * a = ref();
 //Up until this point there is defined results
 //You can even print the address returned
 // but yes probably a bug

 cout << *a << endl;//Undefined results
}
csdnceshi78
程序go I clarified more what I meant by problem, but no it is not dangerous in terms of valid c++ code. But it is dangerous in terms of likely the user made a mistake and will do something bad. Maybe for example you are trying to see how the stack grows, and you only care about the address value and will never dereference it.
10 年多之前 回复
csdnceshi79
python小菜 I disagree: There is a problem before the cout. *a points to unallocated (freed) memory. Even if you don't derefence it, it is still dangerous (and likely bogus).
10 年多之前 回复

It works because the stack has not been altered (yet) since a was put there. Call a few other functions (which are also calling other functions) before accessing a again and you will probably not be so lucky anymore... ;-)

Pay attention to all warnings . Do not only solve errors.
GCC shows this Warning

warning: address of local variable 'a' returned

This is power of C++. You should care about memory. With the -Werror flag, this warning becames an error and now you have to debug it.

It can, because a is a variable allocated temporarily for the lifetime of its scope (foo function). After you return from foo the memory is free and can be overwritten.

What you're doing is described as undefined behavior. The result cannot be predicted.

共19条数据 1 尾页
立即提问