Behind the scenes of returning value from function c++

Viewed 1856

What is the behind the scenes of returning value from function in c++?

In my understanding whenever func. is called the returning address and stack frame ( with local variables , reveresed order of func. arguments and registers ) is pushed onto call stack.

But what happens when executing encounters return statemenet? e.g

int a( int b ){
   int c = b * 2;
   return c;
}

After encountering return statement, is value of C stored in EAX register, local variables are destroyed and stack frame is removed from call stack, and after that the value in EAX register is moved into "returning address" memory?

Or did i misunderstood this concept?

All help is highly appreciated. Thanks.

3 Answers

BTW, the assembly language is processor dependent. The ARM processor does not have an EAX register.

Compilers may have a standard for passing parameters and returning parameters. The method for returning values from functions is dependent on the implementation (compiler). There is no standard across all compilers.

Unoptimized code
Compilers are designed to take advantage of processor registers.

If the return value fits into a single register, then a register will be used to return the value. Depends on the processor.

For larger objects / values, the compiler has two options: return the object in multiple registers or return a pointer to the value. The pointer can be as simple as an index into the stack or an address to where the value is.

Optimized Code
The compiler may replace your function with a simple processor instruction or even drop the code. In this case, there is no return value.

The compiler may evaluate your function to a constant and place the constant into the executable code; thus requiring no function calls or function returns.

The compiler may decide to inline your function. In this case, there is no return value, similar to an assignment statement. A temporary variable may be used to contain the value or another register.

Further information
For more detailed information, research "compiler theory". There is a nice book with a dragon on it ...

C++ is specified in terms of operations against a theoretical model of computer memory.

It also has a feature known as the "as if" rule. That means that the compiler can generate any code it likes, provided that the overall observable effect is "as if" the code you have written was literally translated to operations against the memory model.

In unoptimised code, the assembler produced is in reality very close to the operations expressed in code, for example gcc might produce the following code for your function:

a(int):                                  # @a(int)
        push    rbp                       
        mov     rbp, rsp                  
        mov     dword ptr [rbp - 4], edi  
        mov     edi, dword ptr [rbp - 4]
        shl     edi, 1
        mov     dword ptr [rbp - 8], edi
        mov     eax, dword ptr [rbp - 8]
        pop     rbp
        ret

and given the following calling code:

extern void foo(int x);

int main()
{
    foo(a(2));
}

The following code might be produced:

main:                                   # @main
        push    rbp
        mov     rbp, rsp
        mov     edi, 2
        call    a(int)
        mov     edi, eax
        call    foo(int)
        xor     eax, eax
        pop     rbp
        ret

In this simple program, the observable effect of the code is that foo will be called with an argument of value 4. The call to a only has one observable side-effect. That is, it's return value is double its input value.

Because the returned value is passed directly into foo and not stored anywhere, we could say that all side effects of calling a are completely consumed by a call to foo.

Therefore, if a compiler knows what a does, it doesn't need to generate code to call it. It can just call foo with the value derived 'as if' calling a(2).

Indeed, adding optimisation gives us this:

main:                                   # @main
        push    rax
        mov     edi, 4           # note: 'as if' a(2)
        call    foo(int)
        xor     eax, eax
        pop     rcx
        ret

The implementation of a in this case (on gcc) is as follows:

a(int):                                  # @a(int)
# 'as if' we created a variable and did some arithmetic, 
# stored the result and then returned the result
        lea     eax, [rdi + rdi]   
        ret

If you are asking in term of curiosity, @Thomas Matthews's answer is very good...

If you have a question regarding a specific scenario, you should check it yourself and see the results, reading assembly code is challenging but sure as hell satisfying.

For example, I compiled the following example using gcc:

int func(int a, int b)
{
    return a + b;
}

int main(int argc, char ** argv)
{
    int a, b;
    a = b = 100;

    int c = func(a, b);
}

As you can see, this is as simple as it gets (Tip: when disassembling, try avoiding printf as it adds a lot of assembly code).

Compile with -ggdb so that it will be easier to work with gdb, and run using gdb <application>, then just add a breakpoint inside your method, wait for it to hit and run the command disassemble. The output will look something like this:

Breakpoint 1, func (a=100, b=100) at program.cpp:3
3           return a + b;
(gdb) disas
Dump of assembler code for function func(int, int):
   0x00000000004004d6 <+0>:     push   %rbp
   0x00000000004004d7 <+1>:     mov    %rsp,%rbp
   0x00000000004004da <+4>:     mov    %edi,-0x4(%rbp)
   0x00000000004004dd <+7>:     mov    %esi,-0x8(%rbp)
=> 0x00000000004004e0 <+10>:    mov    -0x4(%rbp),%edx
   0x00000000004004e3 <+13>:    mov    -0x8(%rbp),%eax
   0x00000000004004e6 <+16>:    add    %edx,%eax
   0x00000000004004e8 <+18>:    pop    %rbp
   0x00000000004004e9 <+19>:    retq
End of assembler dump.

As you can see, here the only thing the compiler does is pop the old base pointer into rbp (pop %rbp) and then jump back to our return address (retq). The result is already stored in a register, so no need in doing anything else.

Related