Memory Overhead of RTTI in C++
Run-Time Type Information (RTTI) in C++ allows programs to safely
query object types at runtime via operators like typeid and
dynamic_cast. While RTTI provides powerful mechanisms for
introspection and type-safe downcasting, it incurs memory overhead in
both binary footprint (disk space/read-only memory) and dynamic
execution memory. This article examines the architectural components
that contribute to RTTI memory usage, breaks down per-class and
per-object costs, and explains where this overhead manifests.
1. Per-Instance Overhead (Objects)
For individual object instances, RTTI typically incurs zero additional memory overhead beyond what is already required for polymorphism.
In standard C++ implementations (such as the Itanium C++ ABI used by GCC and Clang, as well as the MSVC ABI), RTTI is linked directly to the virtual method table mechanism:
- If a class is already polymorphic (contains at least one
virtualfunction), the object already contains a virtual pointer (vptr), which typically occupies 8 bytes on a 64-bit architecture or 4 bytes on a 32-bit architecture. - The runtime uses this existing
vptrto reach both virtual functions and RTTI metadata. Thus, instantiating millions of objects of a polymorphic type consumes no additional per-object RAM when RTTI is enabled. - Non-polymorphic types do not gain a
vptrsimply because RTTI is enabled globally, although usingtypeidon a non-polymorphic object evaluates statically at compile time.
2. Per-Class Overhead (Virtual Tables)
The first direct cost of RTTI appears in the virtual table
(vtable) generated for each polymorphic class:
- Vtable Pointer to Type Info: ABI specifications
designate an entry in the vtable structure (typically at index
-1, right before the virtual function pointers) reserved for a pointer to the type’s descriptor. - Cost: 8 bytes per vtable on 64-bit systems (4 bytes on 32-bit systems). If a class uses multiple inheritance, it may generate multiple vtables or sub-vtables, each carrying its own pointer to type information.
3. Type Descriptor
Metadata (std::type_info)
The largest component of RTTI overhead resides in the static metadata
structures generated by the compiler to describe types. These structures
are stored in read-only data segments (.rodata on ELF
systems, or .rdata on PE/Windows systems).
For every class that requires runtime type resolution, the compiler
emits a specialized descriptor derived from
std::type_info:
- Base
std::type_infoStructure: Contains a pointer to its own virtual table, along with an internal pointer to the type's mangled name string. This occupies roughly 16 to 24 bytes on 64-bit platforms. - Type Name String: A null-terminated string
containing the mangled name of the type (e.g.,
_ZTS3Foo). The length of this string depends directly on the length of the class name, its enclosing namespaces, and template parameters. For heavily templated code, mangled names can span hundreds or thousands of bytes per class. - Inheritance Descriptors: For types involved in
inheritance hierarchies, the compiler emits extended structures (such as
__si_class_type_infofor single inheritance or__vmi_class_type_infofor virtual/multiple inheritance):- Single Inheritance: Adds a pointer to the base class's type descriptor (~8 bytes).
- Multiple or Virtual Inheritance: Includes flags,
counts, base class offsets, and pointers to the descriptors of all
parent classes. This data enables
dynamic_castto navigate complex class hierarchies and adjust pointers correctly at runtime.
4. Summary of Overhead Characteristics
- Per Object: 0 bytes (reuses the existing
vptr). - Per Vtable: 8 bytes (pointer to
std::type_info). - Per Class (Metadata): Typically 40 to 100+ bytes for basic classes, scaling up significantly with long names, extensive template specializations, and complex multiple inheritance hierarchies.
- Binary Footprint: Cumulative growth of the executable or shared library size due to mangled strings and inheritance graphs, which can impact instruction cache efficiency and startup memory footprint (working set).
In resource-constrained environments such as embedded systems,
operating system kernels, or high-performance game engines, this static
footprint across thousands of types often motivates disabling RTTI via
compiler flags such as -fno-rtti (GCC/Clang) or
/GR- (MSVC).