[c++] Can a local variable's memory be accessed outside its scope?

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?

The answer is


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.


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!


Did you compile your program with the optimiser enabled? The foo() function is quite simple and might have been inlined or replaced in the resulting code.

But I agree with Mark B that the resulting behavior is undefined.


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.


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


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
}

After returning from a function, all identifiers are destroyed instead of kept values in a memory location and we can not locate the values without having an identifier.But that location still contains the value stored by previous function.

So, here function foo() is returning the address of a and a is destroyed after returning its address. And you can access the modified value through that returned address.

Let me take a real world example:

Suppose a man hides money at a location and tells you the location. After some time, the man who had told you the money location dies. But still you have the access of that hidden money.


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... ;-)


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.


Your code is very risky. You are creating a local variable (wich is considered destroyed after function ends) and you return the address of memory of that variable after it is destoyed.

That means the memory address could be valid or not, and your code will be vulnerable to possible memory address issues (for example segmentation fault).

This means that you are doing a very bad thing, becouse you are passing a memory address to a pointer wich is not trustable at all.

Consider this example, instead, and test it:

int * foo()
{
   int *x = new int;
   *x = 5;
   return x;
}

int main()
{
    int* p = foo();
    std::cout << *p << "\n"; //better to put a new-line in the output, IMO
    *p = 8;
    std::cout << *p;
    delete p;
    return 0;
}

Unlike your example, with this example you are:

  • allocating memory for int into a local function
  • that memory address is still valid also when function expires, (it is not deleted by anyone)
  • the memory address is trustable (that memory block is not considered free, so it will be not overridden until it is deleted)
  • the memory address should be deleted when not used. (see the delete at the end of the program)

It's 'Dirty' way of using memory addresses. When you return an address (pointer) you don't know whether it belongs to local scope of a function. It's just an address. Now that you invoked the 'foo' function, that address (memory location) of 'a' was already allocated there in the (safely, for now at least) addressable memory of your application (process). After the 'foo' function returned, the address of 'a' can be considered 'dirty' but it's there, not cleaned up, nor disturbed/modified by expressions in other part of program (in this specific case at least). A C/C++ compiler doesn't stop you from such 'dirty' access (might warn you though, if you care). You can safely use (update) any memory location that is in the data segment of your program instance (process) unless you protect the address by some means.


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.


You never throw a C++ exception by accessing invalid memory. You are just giving an example of the general idea of referencing an arbitrary memory location. I could do the same like this:

unsigned int q = 123456;

*(double*)(q) = 1.2;

Here I am simply treating 123456 as the address of a double and write to it. Any number of things could happen:

  1. q might in fact genuinely be a valid address of a double, e.g. double p; q = &p;.
  2. q might point somewhere inside allocated memory and I just overwrite 8 bytes in there.
  3. q points outside allocated memory and the operating system's memory manager sends a segmentation fault signal to my program, causing the runtime to terminate it.
  4. You win the lottery.

The way you set it up it is a bit more reasonable that the returned address points into a valid area of memory, as it will probably just be a little further down the stack, but it is still an invalid location that you cannot access in a deterministic fashion.

Nobody will automatically check the semantic validity of memory addresses like that for you during normal program execution. However, a memory debugger such as valgrind will happily do this, so you should run your program through it and witness the errors.


This behavior is undefined, as Alex pointed out--in fact, most compilers will warn against doing this, because it's an easy way to get crashes.

For an example of the kind of spooky behavior you are likely to get, try this sample:

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

void b( int *c )
{
   int y = 29;
   *c = 123;
   cout << "y=" << y << endl;
}

int main()
{
   b( a() );
   return 0;
}

This prints out "y=123", but your results may vary (really!). Your pointer is clobbering other, unrelated local variables.


That's classic undefined behaviour that's been discussed here not two days ago -- search around the site for a bit. In a nutshell, you were lucky, but anything could have happened and your code is making invalid access to memory.


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.


The things with correct (?) console output can change dramatically if you use ::printf but not cout. You can play around with debugger within below code (tested on x86, 32-bit, MSVisual Studio):

char* foo() 
{
  char buf[10];
  ::strcpy(buf, "TEST”);
  return buf;
}

int main() 
{
  char* s = foo();    //place breakpoint & check 's' varialbe here
  ::printf("%s\n", s); 
}

In C++, you can access any address, but it doesn't mean you should. The address you are accessing is no longer valid. It works because nothing else scrambled the memory after foo returned, but it could crash under many circumstances. Try analyzing your program with Valgrind, or even just compiling it optimized, and see...


You actually invoked undefined behaviour.

Returning the address of a temporary works, but as temporaries are destroyed at the end of a function the results of accessing them will be undefined.

So you did not modify a but rather the memory location where a once was. This difference is very similar to the difference between crashing and not crashing.


Examples related to c++

Method Call Chaining; returning a pointer vs a reference? How can I tell if an algorithm is efficient? Difference between opening a file in binary vs text How can compare-and-swap be used for a wait-free mutual exclusion for any shared data structure? Install Qt on Ubuntu #include errors detected in vscode Cannot open include file: 'stdio.h' - Visual Studio Community 2017 - C++ Error How to fix the error "Windows SDK version 8.1" was not found? Visual Studio 2017 errors on standard headers How do I check if a Key is pressed on C++

Examples related to memory-management

When to create variables (memory management) How to check if pytorch is using the GPU? How to delete multiple pandas (python) dataframes from memory to save RAM? Is there a way to delete created variables, functions, etc from the memory of the interpreter? C++ error : terminate called after throwing an instance of 'std::bad_alloc' How to delete object? Android Studio - How to increase Allocated Heap Size Implementing IDisposable correctly Calculating Page Table Size Pointer-to-pointer dynamic two-dimensional array

Examples related to local-variables

Returning string from C function Access a function variable outside the function without using "global" Default values and initialization in Java Can a local variable's memory be accessed outside its scope? What's the scope of a variable initialized in an if statement? How to declare a variable in SQL Server and use it in the same Stored Procedure

Examples related to dangling-pointer

Can a local variable's memory be accessed outside its scope?