Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 343
DanMaklen Avatar asked Jan 28 '17 14:01

DanMaklen


2 Answers

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.

like image 173
Sam Varshavchik Avatar answered Sep 18 '22 12:09

Sam Varshavchik


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.

like image 35
Jack Avatar answered Sep 19 '22 12:09

Jack