Hello! Welcome back.
In our last lesson, we discovered that non-virtual member functions are a "zero-cost abstraction." They are resolved at compile time and don't add to an object's size because their code is stored once in the program's text segment, just like a normal C function. We concluded that the compiler achieves this by secretly passing the object's address as a hidden argument to the function.
Today, we will dive deep into this mechanism. Our learning outcome is to explain the 'this' pointer's implementation and how it's passed as a hidden argument to member functions. We'll formally name that hidden argument, see how to use it explicitly, and, most importantly, investigate how the C++ compiler and the underlying system ABI work together to pass it at the machine level.
The this Pointer: A Formal Introduction
That hidden pointer we discussed is a C++ keyword: this. Inside any non-static member function, this is a pointer that holds the memory address of the object on which the function was called.
Consider this simple class:
class MyObject {
int data;
public:
void setData(int value) {
// These two lines are equivalent
data = value; // Implicit use of 'this'
this->data = value; // Explicit use of 'this'
}
};
int main() {
MyObject obj1;
MyObject obj2;
// When this call is made...
obj1.setData(42); // ...inside setData, 'this' will hold the address of obj1.
// When this call is made...
obj2.setData(99); // ...inside setData, 'this' will now hold the address of obj2.
}
The compiler automatically uses this to resolve member names, so you rarely need to type this-> explicitly. However, it's always there, providing the context for the function to operate on the correct instance's data.
This concept isn't just a C++ abstraction; it's a fundamental part of how compilers implement member functions.
Using Assembly Language Functions
The following resource from Digital Mars, a C++ compiler vendor, provides a concise, technical confirmation of this mechanism. It's a good example of the kind of documentation you'd consult for low-level details.
Read the section titled 'Implications of Type-Safe Linkage'. Focus on the final bullet point which directly states the role of the this pointer.
As the document states, a non-static member function has an "additional hidden argument, the this pointer." Now, let's see exactly how that argument is passed.
The Proof: Calling a Member Function from Assembly
To truly understand the implementation, we'll go to the boundary between C++ and assembly. The following video demonstrates how to call a C++ member function from handwritten assembly code. This exercise forces us to manually do what the C++ compiler does automatically, revealing the underlying mechanics.
ASM Tricks Bonus Vid: C++ Calling Member Methods
This video by Creel provides a practical, step-by-step guide to calling a C++ member method from assembly. It perfectly illustrates the concepts of name mangling and the hidden this pointer.
Watch the video from the beginning to the end (around 09:44). Pay close attention to these key steps: Name Mangling (04:12): Notice how he finds the 'real' function name that the linker sees. This is the unique identifier we discussed in the last lesson. The Hidden Parameter (06:31): The presenter explicitly states that member functions have a hidden first parameter passed in RCX, and that this parameter is the this pointer. The Call (07:23): See how he loads the object's address into RCX (it happens to be there already in his setup) and then calls the mangled function name. Verification (08:50): The program prints the correct value, proving that the assembly code successfully called the member function and modified the object's state via the this pointer.
This demonstration is concrete proof. To call a member function, you need two things: the function's (mangled) address, and the object's address, which you must place in the correct location—in this case, the RCX register—before making the call.
The "How": A Matter of Calling Convention
The video showed the this pointer being passed in the RCX register. This is correct for the environment he is using (Visual Studio on 64-bit Windows). However, the choice of register is not arbitrary; it's dictated by a formal set of rules called an Application Binary Interface (ABI). An ABI standardizes many low-level details, including how function arguments are passed.
For x86-64 architecture, there are two dominant ABIs:
- Microsoft x64 Calling Convention: Used on Windows. The first four integer or pointer arguments are passed in registers
RCX,RDX,R8, andR9. - System V AMD64 ABI: Used on Linux, macOS, and other UNIX-like systems. The first six integer or pointer arguments are passed in registers
RDI,RSI,RDX,RCX,R8, andR9.
Since the this pointer is the hidden first argument, it will always be passed in the register designated for the first argument.
- On Windows, that register is
RCX. - On Linux, that register is
RDI.
This register usage is a critical piece of low-level systems knowledge.

To see a real-world example of a developer working through this on Linux, the following Stack Overflow question is perfect.
Calling-convention for the 'this' parameter on Linux x64
This Stack Overflow post shows a developer trying to call a C++ member function from a low-level context on Linux. Their confusion and the eventual solution perfectly highlight the importance of understanding the ABI.
Read the question and the accepted answer. Notice the initial assumption that this goes in RDI. The confusion arises from a second hidden parameter for the return value (a separate, interesting topic!), but the core fact remains: the this pointer is the first argument, passed in RDI as per the System V ABI.
Test your understanding!
You are debugging a C++ application on a Linux server. You set a breakpoint at the entry point of the following member function:
class NetworkConnection {
public:
void set_timeout(int ms);
// ... other members
private:
int timeout_ms;
};
// ... elsewhere in the code
NetworkConnection conn;
conn.set_timeout(500);
When the breakpoint hits inside set_timeout, which register would you inspect to find the memory address of the conn object?
Show answer
You would inspect the RDI register.
On Linux, the System V AMD64 ABI is used. It dictates that the first argument to a function is passed in RDI. Since the this pointer is the hidden first argument to the set_timeout member function, the address of the conn object will be in RDI. The second argument, ms (with the value 500), would be in the next register, RSI.
Conclusion
In this lesson, we put a name and a mechanism to the "hidden argument" from our previous discussion. We've established that the this pointer is the linchpin that connects a non-static member function to a specific object instance.
Key Takeaways:
- The
thispointer is a hidden first argument passed to every non-static member function, containing the address of the object instance. - The compiler implements this by transforming a member call like
obj.func(arg)into a C-style call likefunc_mangled_name(&obj, arg). - The specific register used to pass
thisis determined by the platform's ABI. For x86-64, this is typicallyRCXon Windows andRDIon Linux/macOS. - Understanding the ABI is crucial for low-level programming, debugging, and interoperability between languages like C++ and assembly.
We now have a solid grasp of how ordinary member functions are laid out in memory and how they operate on object instances. In the next lesson, we'll turn our attention to two very special member functions: constructors and destructors. We'll explore their crucial role in an object's lifecycle and trace their execution order, especially when dealing with objects that contain other objects.