Search code examples
c++assemblyreferencex86-64return-value

return value Vs reference (in assembly)


After taking a look at a few questions (and answers) regarding this topic, I tried the below simple code in Compiler Explorer.

#include <iostream>


class TwoInts
{
public:
    TwoInts( ) = default;

    const int& getAByRef( ) const;

    int getAByVal( ) const;

private:
    int a;
    int b;
};

const int& TwoInts::getAByRef( ) const
{
    return a;
}

int TwoInts::getAByVal( ) const
{
    return a;
}


int main( )
{
    TwoInts ti;

    const int& num1 { ti.getAByRef( ) };
    const int num2 { ti.getAByVal( ) };

    //std::cout << num1 << ' ' << num2 << '\n';
}

Now I see different codes generated for the two member functions getAByRef and getAByVal:

TwoInts::getAByRef() const:
        mov     rax, rdi
        ret
TwoInts::getAByVal() const:
        mov     eax, DWORD PTR [rdi]
        ret

Can someone explain what those two different assembly instructions are doing?


Solution

  • Each member function gets this pointer as an implicit first function argument, as dictated by Itanium ABI (not to be confused with Itanium architecture) used by GCC. this is passed in the rdi register and a value is returned (if it's trivial, and here it is) in the rax (eax) register according to x86-64 System V ABI (see comments by Peter Cordes below).

    In the first case, when you return a by reference, you're actually returning an address of a. a is the first member, so its address is the same as that of the object, i.e. this. Hence, you just set rax to rdi.

    In the second case, when you return a by value, you need to do actual dereferencing. That's what DWORD PTR [rdi] is doing. DWORD PTR means that you want to fetch 4 bytes (sizeof(int)).

    If you put some data member before a, you'll see an additional offset added to rdi.