Hello! Welcome back to our journey into the internals of C++.
In our last lesson, we established a critical piece of the C++ object model: the memory layout for single inheritance. We saw that a derived class object contains a base class subobject and that when the base class is polymorphic (i.e., has at least one virtual function), the compiler inserts a hidden virtual pointer (vptr) at the very beginning of the object's memory.
Today, we will follow that pointer. Our goal is to analyze the vtable (virtual method table) structure and understand the vptr's placement within an object. We'll move from knowing that the vptr exists to understanding precisely what it points to and how that structure—the vtable—is organized. This is the heart of C++'s runtime polymorphism mechanism.
1. The vptr and vtable: A Fundamental Partnership
The relationship between the vptr and the vtable is the key to dynamic dispatch. Let's be very clear on the distinction:
- The
vptris a pointer that exists within each object of a polymorphic class. If you create 100Dogobjects, you create 100vptrs. - The
vtableis a static lookup table that exists once per class. It's generated by the compiler and is shared by all objects of that class. All 100 of yourDogobjects will havevptrs that point to the same singleDogvtable.
This vtable is typically stored in a read-only data segment of the compiled program, like the .rodata section on Linux, which makes sense since it shouldn't be modified at runtime.
The diagram below illustrates this relationship. An object on the stack or heap contains a vptr which points to the vtable in the read-only data section. The vtable itself is an array of function pointers, and each of these pointers points to executable code in the code section (or .text section).

While the C++ standard doesn't mandate the existence of vptrs and vtables, it is the de-facto implementation used by virtually all major compilers, including GCC and Clang, as specified by the Itanium C++ ABI.
To see a clear explanation of this architecture, watch the following clip.
C++ Inheritance Explained: V-Tables, Virtual Destructors & Edge Cases
The video 'C++ Inheritance Explained' from the DeepDiveDev channel provides an excellent visualization of this concept.
Watch the segment from 05:44 to 07:02. It crisply explains the distinction between the per-object vptr and the per-class vtable, and how the compiler uses this structure to find the right function at runtime.
2. The Anatomy of a vtable in Single Inheritance
At its core, a vtable is an array of function pointers. When a derived class inherits from a base class, it doesn't just inherit members; it also inherits the layout of the vtable. The compiler constructs the vtable for a derived class with these rules:
- Copy: It starts with a copy of the base class's
vtable. - Overwrite: If the derived class overrides a virtual function, the compiler replaces the corresponding pointer in the
vtablewith the address of the derived class's implementation. - Append: If the derived class introduces new virtual functions, their addresses are appended to the end of the
vtable.
Let's consider this simple hierarchy:
class Shape {
public:
virtual void draw() { /* draws a generic shape */ }
virtual float area() { return 0.0; }
// virtual ~Shape() {} // We'll add this later
};
class Circle : public Shape {
public:
void draw() override { /* draws a circle */ } // Overwrites
// area() is inherited
virtual void roll() { /* rolls the circle */ } // Appends
};
The vtables would look like this:
-
vtableforShape:- Index 0:
&Shape::draw - Index 1:
&Shape::area
- Index 0:
-
vtableforCircle:- Index 0:
&Circle::draw(Overwritten pointer) - Index 1:
&Shape::area(Inherited pointer) - Index 2:
&Circle::roll(Appended pointer)
- Index 0:
This layout is crucial. Because the Circle vtable maintains the same layout as the Shape vtable for its initial entries, code holding a Shape* can call draw() or area() using the same index (0 or 1) regardless of whether the pointer points to a Shape or a Circle. This is the essence of how polymorphism is efficiently implemented.
3. Deconstructing the Magic: Building Polymorphism in C
Given your strong background in C, the best way to truly understand this mechanism is to build it yourself. The C++ compiler is essentially automating a design pattern that you can replicate manually using structs, function pointers, and careful memory layout.
The following video does exactly this. It's a powerful demonstration that strips away the C++ "magic" and reveals the mechanical, predictable system underneath.
C++ Inheritance Explained: V-Tables, Virtual Destructors & Edge Cases
The 'C++ Inheritance Explained' video continues with a brilliant segment where it implements polymorphism using only C constructs. This will connect directly with your existing knowledge.
Watch from 07:02 to 10:49. Pay close attention to how they simulate inheritance by embedding structs and how the vptr is manually set in their 'constructor' functions. This perfectly mirrors what the C++ compiler does for you implicitly.
4. Exploring the Full vtable: Hidden Entries and ABI Details
The vtable holds more than just pointers to the virtual functions you declare. The ABI specifies several other important entries that are used for the C++ runtime system. For someone who wants to know how things work from first principles, these details are fascinating.
The following article is a fantastic hands-on guide that uses modern C++ to inspect the memory of an object, find its vptr, and explore the full contents of its vtable.
Visualizing the C++ Object Memory Layout Part 1: Single ...
The article 'Visualizing the C++ Object Memory Layout Part 1: Single Inheritance' by Sofia Belen is an exceptional deep dive. It will guide you through programmatically exploring the vptr and vtable.
Please read the following sections: 'Invoking Functions Directly via Vptr': This shows you the exact casting and pointer arithmetic needed to access the vtable and manually call a virtual function. 'Difference Between Deleting and Complete Object Destructors': This reveals a key implementation detail about virtual destructors—that they often have two entries in the vtable for different use cases. 'Vtable Entries [-1] and [-2]': This uncovers even more hidden entries, including the pointer to Run-Time Type Information (RTTI) and the offset-to-top, which is a hint towards how multiple inheritance is handled.
After working through that article, you'll have a complete picture of the vtable structure under the Itanium C++ ABI, including its "hidden" administrative entries. Specifically, for a derived class D inheriting from a base B:
vtable[-2]:offset_to_top(0 in single inheritance)vtable[-1]:typeinfopointer forD(used bytypeidanddynamic_cast)vtable[0]: Complete object destructor forDvtable[1]: Deleting destructor forDvtable[2]...: Pointers toD's virtual functions, following the copy-overwrite-append rule.
Test your understanding!
Consider the following class hierarchy on a 64-bit system:
class Device {
public:
virtual ~Device() {}
virtual bool ping() = 0; // Pure virtual
virtual void reset() { /* default reset */ }
};
class Router : public Device {
public:
~Router() override {}
bool ping() override { return true; }
// reset() is inherited
virtual void configure_firewall() { /* ... */ }
};
Describe the likely layout of the vtable for the Router class, including any hidden entries discussed in the lesson. List the function pointers in the order they would appear.
Show answer
The vtable for the Router class would likely be laid out as follows:
vtable[-2]:offset_to_top(Value would be 0).vtable[-1]:typeinfopointer for theRouterclass.vtable[0]: Pointer to the complete object destructor forRouter.vtable[1]: Pointer to the deleting destructor forRouter.vtable[2]: Pointer toRouter::ping. This entry overwrites the base class slot forping.vtable[3]: Pointer toDevice::reset. This entry is inherited from the base classvtable.vtable[4]: Pointer toRouter::configure_firewall. This is a new virtual function appended to the end.
Note that even though Device::ping is pure virtual and has no implementation, it still occupies a slot in the Device vtable (often filled with a null pointer or a pointer to a generic error function) to maintain the layout. The Router class must provide an implementation to be concrete.
Conclusion
Today we demystified the core of C++ polymorphism. We moved from the per-object vptr to the shared, per-class vtable and dissected its structure.
Key Takeaways:
- The
vptrin each object points to a single, sharedvtablefor its class, which resides in read-only memory. - A
vtableis primarily an array of function pointers, enabling runtime lookup of virtual functions. - In single inheritance, a derived class's
vtablepreserves the layout of its base'svtable, overwriting pointers for overridden functions and appending pointers for new ones. - The
vtablecontains more than just function pointers; it includes hidden entries for RTTI, destructors, and other ABI-specific information crucial for the C++ runtime.
Now that we understand the data structures involved (vptr and vtable), our next lesson will put them to work. We will trace the sequence of operations in a virtual function call, analyzing the assembly instructions and performance overhead compared to a non-virtual call.