This page documents the Map container implementation and the DynamicMessage system in the C++ runtime. Map containers provide efficient hash-map storage for proto map fields, while DynamicMessage enables runtime construction of messages without compile-time generated code.
For information about the Reflection API used to access map fields dynamically, see Message Processing System. For details about code generation for maps, see Descriptor and Compilation Pipeline.
Protocol Buffers implements maps using a hash table with separate chaining. The implementation uses a two-layer design: an untyped base class (UntypedMapBase) that handles core storage and iteration logic, and a type-safe template wrapper (Map<Key, T>) that provides the user-facing API.
The core UntypedMapBase class manages the hash buckets and nodes, while the templated Map<Key, T> class adds typed key/value semantics.
UntypedMapBase manages the underlying hash table with array of buckets (table_) and linked nodes.NodeBase contains a next pointer for chaining and provides access to the key memory.KeyNode<Key> extends NodeBase by storing the key.Node<Key,T> extends KeyNode<Key> by storing the key-value pair.KeyMapBase<Key> adds typed map operations like FindNodeAndBucket and node insertion.Map<Key,T> is the exposed container API providing operator[], find, insert, and erase.Sources: src/google/protobuf/map.h253-507 src/google/protobuf/map.h726-753
UntypedMapBase stores a compact TypeInfo struct describing the key and value types for generic handling without virtual calls:
| Field | Type | Purpose |
|---|---|---|
uint16_t node_size | Size of node | Total size of allocated node |
uint8_t value_offset | uint8 offset | Offset to value in node |
uint8_t key_type_k:4 | TypeKind enum | Key field kind |
uint8_t val_type_k:4 | TypeKind enum | Value field kind |
TypeKind enum defines supported data kinds:
This allows generic operations on any key/value type recognized by protobuf's type system.
Sources: src/google/protobuf/map.h258-312
Nodes are aligned and structured as:
NodeBase aligned to kMaxMessageAlignment (16 bytes) to allow predictable offset calculation.KeyNode adds the key storage.Node adds the value inside the pair kv.Nodes are allocated via AllocNode(Arena*), using an arena allocator if available, otherwise dynamic heap allocation.
Sources: src/google/protobuf/map.h165-173 src/google/protobuf/map.h460-476
Hash():
absl::HashOf is used.hash & (num_buckets_ - 1).This design balances performance and binary size, and supports a wide range of key/value types.
Sources: src/google/protobuf/map.h702-712 src/google/protobuf/map.cc60-127
MapField maintains both a map container and a repeated field representation internally. The repeated field is used for serialization compatibility with proto wire format, which represents map fields as repeated key/value entry messages.
MapFieldBaseForParse stores a pointer globals_or_payload_ which either points to global default data or a dynamically allocated Payload.Payload contains a repeated field (of MapEntry messages), a synchronization state, and a mutex to protect concurrent access.TypeDefinedMapFieldBase holds the typed map map_.MapField<K,V> exposes the user APIs for map-like access and wraps the above components.Sources: src/google/protobuf/map_field.h104-302 src/google/protobuf/map.h622-681
MapField synchronizes the state between map and repeated field representations:
| State | Map State | Repeated State | Meaning |
|---|---|---|---|
STATE_MODIFIED_MAP | Current | Stale | Map was modified, repeated field needs update |
STATE_MODIFIED_REPEATED | Stale | Current | Repeated field mutated, map needs update |
STATE_CLEAN | Current | Current | Both in sync |
A static atomic function pointer sync_map_with_repeated is installed to handle synchronization logic, enabling separation of the full runtime from lite code.
Sources: src/google/protobuf/map.h656-659 src/google/protobuf/map_field.cc34-64
Synchronization logic converts between Map<Key, T> and the repeated field of MapEntry messages on demand.
Sources: src/google/protobuf/map_field.cc149-229
The pointer globals_or_payload_ distinguishes between global default storage and an allocated payload by using its least significant bit (LSB):
Payload struct (offset by 1).This clever pointer encoding delays allocation of repeated field storage until it is actually needed.
Sources: src/google/protobuf/map_field.h234-255 src/google/protobuf/map.h674-678
DynamicMessage allows construction and use of protocol buffer messages at runtime without compile-time generated message classes. This enables generic handling of arbitrary messages by interpreting their descriptors.
DynamicMessage mimics the memory layout of a compiled message to allow efficient use of Reflection and other generated-message utilities.
TypeInfo structure to match generated message field offsets.GeneratedMessageReflection to operate identically on dynamic and static messages.Sources: src/google/protobuf/dynamic_message.cc173-450
Different types of fields allocate storage as follows:
| Field Type | Storage Type | Initialization |
|---|---|---|
| Singular primitives | Inline (4 or 8 bytes) | Zero initialized |
| Singular strings | ArenaStringPtr or MicroString | Empty string default |
| Singular messages | Message* pointer | nullptr |
| Repeated primitives | RepeatedField<T> | Empty initialized |
| Repeated strings | RepeatedPtrField<string> | Empty initialized |
| Repeated messages | RepeatedPtrField<Message> | Empty initialized |
| Map fields | DynamicMapField (derived from MapFieldBase) | Empty map |
Sources: src/google/protobuf/dynamic_message.cc452-625
Maps inside DynamicMessage are represented by DynamicMapField, derived from MapFieldBase. It contains an UntypedMapBase instance.
DynamicMapField is constructed dynamically using field descriptor default entries.UntypedMapBase::GetTypeInfoDynamic() to configure its behavior based on runtime type information.Sources: src/google/protobuf/dynamic_message.cc90-168
The DynamicMessageFactory maintains a cache of DynamicMessage prototypes (default instances) per message descriptor.
New() calls clone the prototype using MergeFrom() or similar.This caching avoids repeated layout computation and improves runtime performance.
Sources: src/google/protobuf/dynamic_message.cc627-897
DynamicMessage uses GeneratedMessageReflection configured with a detailed ReflectionSchema:
This schema enables full reflection support, including field access, unknown fields, extensions, and oneofs for generically structured messages.
Sources: src/google/protobuf/generated_message_reflection.h118-145 src/google/protobuf/dynamic_message.cc452-625
DynamicMessage parsing uses the fallback generic parser paths:
TcParser::GenericFallbackLite() which interprets the wire format field-by-field via the reflection interface.WireFormat::SerializeWithCachedSizes() with reflection iteration over fields.Therefore, dynamic messages have somewhat reduced performance compared to generated counterparts but provide runtime flexibility.
Sources: src/google/protobuf/generated_message_tctable_lite.cc74-77 src/google/protobuf/dynamic_message.cc899-1080
In Protocol Buffers, map fields are syntactic sugar represented internally as repeated fields of special synthetic messages called MapEntry messages.
For a proto map field:
The compiler synthesizes:
MapEntry message has exactly two fields: key and value.MapField class provides a higher-level map API that wraps this wire format representation.Sources: src/google/protobuf/generated_message_reflection.cc98-101
MapField system resynchronizes between the map container and repeated field views for serialization and parsing.Sources: src/google/protobuf/map_field.cc149-229
This diagram situates the main components:
Map container is wrapped by MapFieldBase/MapField which synchronize with repeated fields.DynamicMessage uses DynamicMapField for map fields embedded in dynamic messages.DynamicMessageFactory caches the prototypes and manages the runtime construction lifecycle.This page described the key implementation details of Protocol Buffers maps and the dynamic message system, providing insight into how runtime flexibility and efficient container semantics are achieved in the C++ runtime.
Refresh this wiki