Understanding C++ Virtual Function Tables (vtable) and Their Memory Layout
This article explains how C++ implements runtime polymorphism through virtual function tables, detailing their structure, memory placement, how they enable dynamic binding, and illustrating usage with single, multiple inheritance and practical code examples.
In C++, polymorphism is realized via virtual functions, and the compiler generates a virtual function table (vtable) that stores addresses of virtual functions for each class. The vtable acts as a directory allowing the program to call the correct function implementation at runtime based on the object's actual type.
Using a game‑character example, a base class class Character { public: virtual void Attack() { std::cout << "Character attacks in a general way." << std::endl; } }; is derived into class Warrior : public Character { public: void Attack() override { std::cout << "Warrior attacks with a sword!" << std::endl; } }; and class Mage : public Character { public: void Attack() override { std::cout << "Mage casts a fireball!" << std::endl; } }; . The compiler creates separate vtables for each class, pointing to the appropriate Attack implementation, so a base‑class pointer invokes the correct derived function without explicit conditionals.
The vtable is stored in the read‑only data segment (.rodata). Each object that contains virtual functions holds a hidden pointer (vptr) at the beginning of its memory layout, typically 4 bytes on 32‑bit systems or 8 bytes on 64‑bit systems. Example code shows how to reinterpret the object address to read the vptr and the vtable address:
#include
class Animal {
public:
virtual void Speak() { std::cout << "Animal makes a sound." << std::endl; }
int m_age;
};
int main() {
Animal dog;
dog.m_age = 5;
int* ptr = reinterpret_cast
(&dog);
int vptr_value = *ptr;
std::cout << "The value of vptr in the dog object: " << std::hex << vptr_value << std::endl;
return 0;
}Further examples demonstrate how the vtable changes with inheritance. In single inheritance without overrides, the derived class’s vtable contains the base class’s function pointers followed by the new virtual functions. When a derived class overrides a base function, the corresponding entry in the vtable is replaced with the derived implementation.
Multiple inheritance introduces multiple vtables, one for each base subobject. A derived class that inherits from Base1 and Base2 will have two vptrs, each pointing to its respective vtable, and the layout interleaves the pointers and data members accordingly.
Practical code shows how to access the vtable directly (though not recommended for production) and how polymorphic calls are resolved at runtime:
#include
class Base { public: virtual void Func1() { std::cout << "Base::Func1" << std::endl; } virtual void Func2() { std::cout << "Base::Func2" << std::endl; } };
typedef void(*FunPtr)();
int main() {
Base obj;
int* vptr = reinterpret_cast
(&obj);
int vtable_address = *vptr;
FunPtr func1_ptr = reinterpret_cast
(*(int*)vtable_address);
func1_ptr();
FunPtr func2_ptr = reinterpret_cast
(*(((int*)vtable_address) + 1));
func2_ptr();
return 0;
}Finally, the article illustrates a typical polymorphic design using an abstract Shape class with a pure virtual Draw method, and concrete Circle , Rectangle , and Triangle classes. A helper function DrawShapes iterates over an array of Shape* pointers and calls Draw , demonstrating how the vtable enables uniform handling of diverse object types and easy extensibility.
Deepin Linux
Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.