DanMaklen
DanMaklen

Reputation: 217

Where is the return object stored?

I generally understand how a function returns an object by value. But I wanted to understand it on the lower level. Assembly level if reasonable.

I understand this this code

ClassA fun(){
    ClassA a;
    a.set(...);
    return a;
}

is transformed internally to

void fun(Class& ret){
    ClassA a;
    a.set(...);
    ret.ClassA::ClassA(a);
}

Which effectively call the copy constructor on the return value.

I also understand that there are some optimizations (like NRVO) that could generate the following code avoiding the copy constructor.

void fun(Class& ret){
    ret.set(...);
}

However my question is a bit more basic. It doesn't really have to do with Objects in specific. It could be even primitive types.

Lets say we have this code:

int fun(){
   return 0;
}
int main(){
    fun();
}

My question is where is the return object stored in memory.

If we look at the stack... There is the stack frame of main and then the stack frame of fun. Is the return object stored in some address like maybe between the two stack frames? Or maybe it is stored somewhere in the main stack frame (and possibly that's the address which is passed by reference in the generated code).

I have thought about it and the second one seems more practical however I don't understand how the compiler know how much memory to push in the stack frame of main? Does it calculate what is the largest return type and push that even though there could be some wasted memory? Or is it done dynamically, it allocates that space only before the function is called?

Upvotes: 14

Views: 3016

Answers (3)

Mike Nakis
Mike Nakis

Reputation: 61993

In the following code:

int fun()
{
   return 0;
}

The return value is stored in a register. On intel architectures, this will usually be ax (16-bit), or eax (32-bit), or rax (64-bit). (Historically known as the accumulator.)

If the return value was a pointer or a reference to an object, it would still be returned through that register.

If the return value is larger than a machine word, then the ABI (Application Binary Interface) may require another register to be used to hold the high-order word. So, if you are returning a 32-bit quantity on a 16-bit architecture, dx:ax will be used. (And so on for larger quantities in bigger architectures.)

Larger return values are passed by other means, like the void fun(Class& ret) mechanism that you are already aware of.

Passing return values via the accumulator register is very efficient and it is a somewhat strong convention, almost all ABIs that I have seen require it.

Upvotes: 4

Jack
Jack

Reputation: 133577

The answer is ABI specific but generally the calls are compiled with an hidden parameter which is the pointer to the memory that the function should use, like you said suppose the function is compiled as

void fun(Class& ret){
    ClassA a;
    a.set(...);
    ret.ClassA::ClassA(a);
}

Then at call site you will have something like

Class instance = fun();
fun(instance);

Now this makes the caller reserve sizeof(Class) bytes on the stack and pass that address to the function so that fun can "fill" that space.

This is no different from how the stack frame of the caller would reserve space for its own locals, the only difference is that the address to one of its locals is passed to fun.

Mind that if sizeof(Class) is less than the size of a register (or a couple of registers) it is totally possible that value is returned directly inside them.

Upvotes: 11

Sam Varshavchik
Sam Varshavchik

Reputation: 118340

The C++ language specification does not specify these low level details. They are specified by each C++ implementation, and the actual implementation details vary from platform to platform.

In nearly every case, a return value that's a simple, native type gets returned in a certain, designated, CPU register. When a function returns a class instance, the details vary, depending on the implementation. There are several common approaches, but the typical case would be the caller being responsible for allocating sufficient space for the return value on the stack, before calling the function and passing an additional hidden parameter, to the function, where the function is going to copy the returned value (or construct it, in case of RVO). Or, the parameter is implicit, and the function can find the space, on the stack, for the return value itself, after the call's stack frame.

It's also possible that a given C++ implementation will still use a CPU register to return classes that are small enough to fit into a single CPU register. Or, perhaps, a few CPU registers are reserved for returning slightly bigger classes.

The details vary, and you will need to consult the documentation for your C++ compiler, or your operating system, to determine the specific details that apply to you.

Upvotes: 19

Related Questions