why its not safe to return value on function stack

2019-06-24 06:08发布

问题:

I came across the following paragraph while reading bruce eckel..where he was trying to explain why its not safe for function to return value on stack

Now imagine what would happen if an ordinary function tried to return values on the stack
.you can,t touch any part of the stack that's above the return address,so the function would have to push the values below the return address.But when the assembly language return is executed ,the stack pointer must be pointing to the return address(or right below it depending on your machine),so right before the RETURN ,function must move the stack pointer up,thus clearing of all the local variables.If you are trying to return values on the stack below the return address,you become vulnerable at the moment because an interrupt could come along.The ISR would come the stack pointer down to hold its return address and its local variables and overwrite your return value

would you like to help me for comprehend the bold italic text?

回答1:

Suppose that you have the following call stack somewhere in your application:

  1. Main routine
  2. Function1's local variables
  3. Function2's local variables <-- STACK POINTER

In this case main calls function1, and function1 calls function2.

Now suppose that function2 calls function3, and the return value of function3 is returned on the stack:

  1. Main routine
  2. Function1's local variables
  3. Function2's local variables
  4. Function3's local variables, including the return value <-- STACK POINTER

Function3 stores the return value on the stack, and then returns. Returning means, decreasing the stack pointer again, so the stack becomes this:

  1. Main routine
  2. Function1's local variables
  3. Function2's local variables <-- STACK POINTER

You see, function3's stack frame is not here anymore.

Well, actually I lied a bit. The stack frame is still there:

  1. Main routine
  2. Function1's local variables
  3. Function2's local variables <-- STACK POINTER
  4. Function3's local variables, including the return value

So it seems safe to still access the stack to get the return value.

But, if there is an interrupt AFTER function3 has returned, but BEFORE function2 get's the return value from the stack, we get this:

  1. Main routine
  2. Function1's local variables
  3. Function2's local variables
  4. Interrupt function's local variables <-- STACK POINTER

And now the stack frame is really overwritten, and the return value that we desperately needed has gone.

That's why returning a return value on the stack is not safe.

The problem is similar to the one shown in this simple piece of C code:

char *buf = (char *)malloc(100*sizeof(char *));
strcpy (buf, "Hello World");
free (buf);
printf ("Buffer is %s\n",buf);

Most of the times, the memory that was used for buf will still have the contents "Hello World", but it can go horribily wrong if someone is able to allocate memory after free has been called, but before printf is called. One such example is in multi-threaded applications (and we already encountered this problem internally), like shown here:

THREAD 1:                                  THREAD 2:
---------                                  ---------
char *buf = (char *)malloc(100);
strcpy (buf, "Hello World");
free (buf);
                                           char *mybuf = (char *)malloc(100);
                                           strcpy (mybuf, "This is my string");
printf ("Buffer is %s\n",buf);

The printf is Thread 1 may now print "Hello World", or it may print "This is my string". Anything can happen.



回答2:

He is just trying to explain why you shouldn't return a pointer or reference to a local variable. Because it disappears as soon as the function returns!

Exactly what happens at the hardware level isn't that important, even if it might explain why the value sometimes seems to still be there and sometimes not.



回答3:

When you call a function that has pass-by-stack arguments, those arguments get pushed onto the stack. When the function returns, that bit of stack memory it was using is released. Immediately thereafter, it's unsafe to access what was in those stack values, because something else may have overwritten them.

Let's say we're on a cpu where the stack pointer is kept in a register called SP, and it grows "upwards".

  1. Your code is chugging along and comes to a function call. At this point, we'll say SP is 100.
  2. The function is called, and your function takes two single byte arguments. Those two bytes worth of arguments get pushed onto the stack, and... and this is the important part - the address of the code from which you called the function (let's say it's 4bytes). Now SP is 106. The address to return to is at SP=100, and your two bytes are at 104 and 105.
  3. Let's say the function modifies one of those arguments (SP=105) as a way to return the modified value
  4. The function returns, the stack snaps back to where it was (SP=100), and continues on.

  5. In a perfect world, there's nothing else going on in the system and your program has absolute control over the CPU... Until you do something else that requires the stack, that SP=105 value will stay there "forever".

  6. However, with interrupts, there's no guarantees that something else won't come up. let's say a hardware interrupt hits your app. This means an immediate jump to the interrupt servicing routine, so the current address of where the CPU was when the interrupt hit gets pushed onto the stack (4 bytes), now SP is 103. Let's say this ISR calls other subroutines, which means more return addresses get pushed onto the stack. So now SP is 107... your original 105 value has no been overwritten.

  7. eventually these ISRs will return, control goes back to your code, and SP is 100 again... your app tries to retrieve that SP=105 value, blissfully unaware that it got trashed by the ISR, and now you're working with bad data.



回答4:

The most important part of that paragraph is:

If you are trying to return values on the stack below the return address (...)

In other words, don't return pointers to data that is only valid within the scope of that function.

This was probably something you had to worry about before C standardized how a function returned a struct by value. Now, it's part of the C99 standard (6.8.6.4) and you shouldn't worry about it.

And return by value is fully supported in C++ for a while now. Otherwise, many STL implementation details would simply not work properly.



标签: c++ stack return