|
|
|
|
Re-entrancy
The problem occurs because functions in C
and C++ support interrupts; that is, the languages are
re-entrant. They also support recursive function
calls. This means that at any point in the execution of a program an interrupt
can occur without breaking the program. Of course, the person who writes the
interrupt service routine (ISR) is responsible for
saving and restoring all the registers that are used in the ISR, but if the ISR
needs to use any memory further down on the stack, this must be a safe thing to
do. (You can think of an ISR as an ordinary function with no arguments and
void return value that saves and restores the CPU state. An ISR function
call is triggered by some hardware event instead of an explicit call from within
a program.)
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, the
function must move the stack pointer up, thus clearing off all its local
variables. If you’re trying to return values on the stack below the return
address, you become vulnerable at that moment because an interrupt could come
along. The ISR would move the stack pointer down to hold its return address and
its local variables and overwrite your return value.
To solve this problem, the caller
could be responsible for allocating the extra storage on the stack for
the return values before calling the function. However, C was not designed this
way, and C++ must be compatible. As you’ll see shortly, the C++ compiler
uses a more efficient scheme.
Your next idea might be to return the
value in some global data area, but this doesn’t work either. Reentrancy
means that any function can be an interrupt routine for any other function,
including the same function you’re currently inside. Thus, if you
put the return value in a global area, you might return into the same function,
which would overwrite that return value. The same logic applies to
recursion.
The only safe place to return values is
in the registers, so you’re back to the problem of what to do when the
registers aren’t large enough to hold the return value. The answer is to
push the address of the return value’s destination on the stack as one of
the function arguments, and let the function copy the return information
directly into the destination. This not only solves all the problems, it’s
more efficient. It’s also the reason that, in
PassingBigStructures.cpp, the compiler pushes the address of B2
before the call to bigfun( ) in main( ). If you look at
the assembly output for bigfun( ), you can see it expects this
hidden argument and performs the copy to the destination inside the
function.
|
|
|