C++ features in assembly recap

Well done! This chapter showed that the features that make C++ feel like a different language from C all compile down to machinery you already knew: hidden pointer arguments, inserted calls, tables of function pointers, and ordinary loads and stores. Let's review the key concepts from this chapter.

Member Functions and the this Pointer

A member function compiles to an ordinary function whose hidden first argument is the this pointer, the address of the object, passed in rdi; explicit arguments shift one slot right, so the first goes in rsi. At the call site, obj.method() loads &obj into rdi (often via lea) before the call, even when you wrote no arguments. Inside the function, m_value means this->m_value, a fixed-displacement access using the struct-layout offsets from Chapter 5. Raw symbols are mangled names like _ZN7Counter9incrementEv, encoding class, function, and parameter types; a static member function has no this at all, and access specifiers vanish entirely because there is no object-oriented machine code.

Constructors, Destructors, and RAII

Logger log{}; compiles to a constructor call you never wrote: the object's stack address in rdi, constructor arguments in rsi onward. The compiler inserts a destructor call at scope exit and duplicates it onto every exit path, including early returns, which is why RAII is visible in a listing: the acquire and release calls physically bracket the work in the scope.

call LockGuard::LockGuard(Mutex&)   ; acquire
...                                  ; the work
call LockGuard::~LockGuard()        ; release, on every path

Trivial construction compiles to plain movs or, at -O2, often to nothing; the class abstraction itself is free. Mangled constructors come in C1 (complete object) and C2 (base object) variants, identical and often aliased for simple classes.

Virtual Functions and Vtables

A virtual call through a base pointer or reference cannot name its target, so it compiles to a double indirection: load the hidden vtable pointer from the object's first 8 bytes (mov rax, [rdi]), load the function pointer from slot n at offset 8*n (mov rax, 8[rax]), then call rax, an indirect call. That hidden pointer shifts every data member past offset 8. There is exactly one vtable per class, in .rodata under a _ZTV... label, and the constructor installs the pointer to it, which is why an object's dynamic type is whichever constructor ran. When the optimizer can prove the dynamic type, it devirtualises back to a direct call or inlines the body entirely.

Templates in Assembly

A template that is never instantiated produces no assembly at all: it is a recipe, not a function. Each instantiation is its own fully concrete function, so maxOf<int> uses cmp and general-purpose registers while maxOf<double> uses comisd and xmm registers, and the template arguments are baked into the mangled name (Ii...E for <int>). Calls to template functions are direct calls resolved at compile time, the opposite of the vtable's load-load-call rax, which is why templates are called static polymorphism. The price is code bloat, one copy per distinct type argument, though the linker deduplicates identical instantiations across files, and at -O2 small templates usually inline and vanish.

A Glimpse of Exceptions

throw compiles to a call to __cxa_allocate_exception (sized like a new), a store of the thrown value, and a call to __cxa_throw with the exception object and its type info (a _ZTI... label); __cxa_throw never returns. The try body compiles to completely normal code, while the catch machinery appears as landing pads after the function's normal code, bracketed by __cxa_begin_catch and __cxa_end_catch. Under the zero-cost model no extra instructions execute on the happy path; the cost lives in static unwind tables and is paid heavily when a throw actually happens. As the unwinder walks the stack it visits cleanup landing pads that call local objects' destructors, which is how RAII holds on the exceptional exit path.

Key Terminology

  • this pointer: The hidden first argument of a member function, the object's address, passed in rdi
  • Mangled name: The encoded symbol a compiler emits (e.g. _ZN7Counter8setValueEi), decodable with c++filt
  • Static member function: A member function with no this pointer; its first explicit argument takes rdi
  • Constructor / destructor: Member functions the compiler calls for you, at object creation and at every scope exit
  • RAII: Tying a resource to an object's lifetime; visible as constructor and destructor calls bracketing a scope
  • Trivial type: A type whose construction needs no call, only plain stores (or nothing at -O2)
  • vtable: A per-class array of function pointers in .rodata, labelled _ZTV..., one slot per virtual function
  • vtable pointer: The hidden 8-byte pointer at offset 0 of every object of a class with virtual functions
  • Direct call: A call to a fixed label resolved at link time
  • Indirect call: call rax, jumping to whatever address the register holds; the shape of virtual dispatch
  • Devirtualisation: The optimizer replacing a virtual call with a direct call when it can prove the dynamic type
  • Instantiation: The concrete function generated from a template for specific type arguments
  • Static polymorphism: Choosing code by type at compile time via templates, with no run-time dispatch
  • Code bloat: The binary-size cost of one code copy per template instantiation
  • Landing pad: A labelled block after a function's normal code, reached only by the unwinder
  • Unwinder: The runtime that walks stack frames after a throw, running destructors until a matching catch
  • Zero-cost exception model: Table-driven unwinding that adds no instructions to the non-throwing path

Looking Forward

You have now read every major C++ construct in its compiled form, mostly at -O0, where the listing follows the source line by line. Chapter 7 turns to the optimizer: constant folding, dead code elimination, inlining, loop unrolling, and vectorisation, the transformations that make -O2 listings so much shorter and stranger than their sources. It all builds to the capstone, where you will read a real optimized function from start to finish.