I am somewhat confused regarding exactly when the context of a called function gets deleted. I have read that the stackframe of the called function is popped off when it returns. I am trying to apply that knowledge to the following scenario where function foo
calls function bar
, which return a structure. The code could look something like this:
//...
struct Bill
{
float amount;
int id;
char address[100];
};
//...
Bill bar(int);
//...
void foo() {
// ...
Bill billRecord = bar(56);
//...
}
Bill bar(int i) {
//...
Bill bill = {123.56, 2347890, "123 Main Street"};
//...
return bill;
}
The memory for bill
object in the function bar
is from the stackframe of bar
, which is popped off when bar
returns. It appears to be still valid when the assignment of the returned structure is made to billRecord
in foo
.
So does it mean that the stackframe for bar
is not deleted the instant it returns but only after the value returned by bar
is used in foo
?
You're right, there's this "hole" between when bar
returns and foo
copies the return value somewhere with an assignment operation. The way this works is that there is notionally some 'return value' space where the return value lives while being returned. So from the execution model, there are two copies of the return value -- from the local in bar
to the return space and from the return space to billRecord
in foo.
Exactly how this works depends on the calling conventions. On x86_64, the "return value space" is in registers for small return values and in some memory controlled by the caller for larger return values. If the return value is larger than two registers worth, then the caller must pass a 'hidden' extra argument with a pointer to the space where the return value should be stored. bar
will then copy its local variable into that space before deleting its stack frame and returning.
So when compiling foo
the compiler knows it needs to provide that extra hidden argument and knows it needs to allocate some space for it. If it is smart (and you enable optimization) it will simply re-use the space for billRecord
for this (passing a pointer to billRecord
as the hidden argument), and the assignment in foo
will then be a noop (as it knows bar
will do all the work)1`.
If the compiler is smart when compiling bar
it might do "return value optimization" and, realizing it is just going to return the local variable bill
, allocate that local var in the return value space it got from its caller, rather than in its own stack frame.
1Of course, it can only do this if it knows there's no way for bar
to access billRecord
directly. This requires what is known as "escape analysis" -- if the location of billRecord
"escapes" from foo
(for example, by taking its address and storing it somewhere or passing it as an argument somewhere), this optimization can't be done and it will need to allocate additional space in its stack frame for the return space in addition to that used by billRecord