C++ Small String Optimization Memory Layout
Small String Optimization (SSO) is a compiler optimization technique
in C++ that eliminates dynamic memory allocation for short strings by
storing their characters directly inside the std::string
object itself. In traditional implementations, every string allocates
memory on the heap, incurring allocation overhead and cache misses. SSO
alters the string's internal memory layout by using a union
structure that repurposes the space normally reserved for heap pointers
and capacity tracking into an inline character buffer, fundamentally
changing where and how character data is stored in memory.
The Traditional std::string Memory Layout
Without SSO, a standard std::string object on a 64-bit
architecture typically requires 24 bytes of stack space to manage
heap-allocated data:
- Data Pointer (8 bytes): A pointer
(
char*) holding the address of the dynamically allocated buffer on the heap. - Size (8 bytes): A
size_trepresenting the current number of characters in the string. - Capacity (8 bytes): A
size_trepresenting the total allocated capacity of the heap buffer before reallocation is required.
In this model, even a single-character string like "a"
triggers a call to the heap allocator (such as malloc or
operator new), creating memory fragmentation and requiring
an extra pointer dereference whenever the data is accessed.
How SSO Alters the Memory Architecture
SSO eliminates heap allocation for small strings by defining a layout
that overlaps the heap management fields with a local character array.
The standard library achieves this using a union.
When a string's length is within the SSO threshold, the memory layout transitions from holding remote heap metadata to acting as a self-contained byte array:
- Elimination of the Heap Pointer: The 8 bytes
normally occupied by the heap pointer are repurposed as the beginning of
an inline
char[]buffer. - Repurposing Capacity/Size Fields: Depending on the implementation, the bytes allocated for tracking capacity or size are merged or partially overwritten to extend the inline buffer capacity.
- Discriminator Flag: A single bit or byte is used to indicate whether the string is currently in "short" (inline) or "long" (heap-allocated) mode.
Because these representations share the same memory footprint through
a union, the overall sizeof(std::string) remains constant
whether the string is empty, short, or massive.
Common Standard Library Implementations
Different C++ Standard Library implementations structure this memory alteration differently, trading off object size against maximum inline capacity.
GCC (libstdc++)
In GCC's libstdc++, std::string is 32 bytes
on 64-bit systems:
- Layout: It holds a pointer to the character data, an 8-byte size field, and a 16-byte union.
- The Union: The union holds either the 8-byte
capacity integer (for heap strings) or a 16-byte local array
(
char[16]). - SSO Capacity: It can store up to 15 characters plus
the null terminator (
\0). When in SSO mode, the main data pointer simply points back to the internal 16-byte buffer inside the object itself.
Clang (libc++)
Clang's libc++ minimizes the total size of
std::string to 24 bytes on 64-bit systems while maximizing
SSO capacity:
- Layout: It uses a union of two structs: one for long strings and one for short strings.
- Long String Struct: Contains an 8-byte capacity, an 8-byte size, and an 8-byte heap pointer.
- Short String Struct: Uses 1 byte to store the string size and flags, and the remaining 23 bytes as an inline character array.
- SSO Capacity: It stores up to 22 characters plus the null terminator. It uses the least significant bit of the first byte to differentiate between small and large mode.
MSVC (Microsoft Visual C++)
MSVC adopts a 32-byte layout similar to GCC:
- Layout: It maintains an internal 16-byte character buffer, an 8-byte pointer, an 8-byte size, and an 8-byte capacity.
- SSO Capacity: It stores up to 15 characters plus the null terminator. If the string exceeds 15 characters, it activates the pointer and allocates memory dynamically, keeping the 16-byte buffer unused.
Implications on Performance and Behavior
The altered memory layout directly affects how
std::string performs at runtime:
- Cache Locality: Small strings reside entirely on
the stack (or inside the enclosing object that owns the
std::string). This guarantees sequential memory access and prevents CPU cache misses caused by dereferencing heap pointers. - Zero Allocation Latency: Creating, copying, and destroying small strings requires no interaction with the operating system's memory manager, avoiding lock contention in multi-threaded applications.
- Move Semantics Difference: Moving a heap-allocated
std::stringis an \(O(1)\) operation involving a simple pointer swap. Moving an SSO-allocated string requires copying the characters viamemcpy, since the data is physically tied to the memory address of the source object. However, copying up to 23 bytes is typically faster than the pointer management and branch checks of dynamic allocation.