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?
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
.