Skip to content

Instantly share code, notes, and snippets.

@ParadoxV5
Last active October 31, 2025 04:42
Show Gist options
  • Save ParadoxV5/f84d2c328e12b9d3b2e5174cc7fd6c07 to your computer and use it in GitHub Desktop.
Save ParadoxV5/f84d2c328e12b9d3b2e5174cc7fd6c07 to your computer and use it in GitHub Desktop.
[WIP] `MariaDB/server`’s Standard Library and comparison with C++’s

MariaDB is primarily written in C++. — or are we? Hah, you will be surprised with how little C++ features we use.

MariaDB/server’s Standard Library and comparison with C++’s

my_global.h

TODO: https://jira.mariadb.org/browse/MDEV-35460

THD

TODO: explore the struct & specialized threads such as in Replication

Allocators

MariaDB/server has many “levels” for allocating heap memory, not to mention individual optimizations such as the Tranx_node_allocator @ sql/semisync_master.h.

malloc(size_t)/free(void*) @ <stdlib.h>

Despite the many levels, some code still uses malloc(size_t) or new for memory that are not expected to last long (as in how Garbage Collectors finds most objects die young).

sf_malloc(size_t, myf)/sf_free(void*) @ mysys/mysys_priv.h

If the SAFEMALLOC compile define is not enabled, sf_malloc() and sf_free() are simply macros that delegate to malloc()/free(). If it is, though, then this function is an interface to malloc(size_t)` that

  • Requires the requested size to not exceed SIZE_T_MAX - 2^24
  • Adds canaries around the requested memory to detect buffer overruns
    • Huh, sounds like the job of sanitizers if the platform doesn’t already have some built-in.
  • Tracks some staticstics
  • Fills the requested memory if the TRASH_FREED_MEMORY compile define is enabled – with 0xA5 bytes (¥) on allocation and 0x8F (C1 SS3) on free.

my_malloc(PSI_memory_key, size_t, myf)/my_free(void*) @ include/my_sys.h

Memory that are expected to last are allocated from this function or a higher level. In addition to calling sf_malloc(), my_malloc() also:

  • Allocates 1 byte if 0 bytes are requested
    • Compatibility with size 0 should be the caller’s concern, in my opinion.
  • Requires the requested size to not exceed SIZE_T_MAX - 2^24
  • Tracks memory usage for the Performance Schema
    • PSI_memory_key is appearently a category ID in the memory use instrumenation.
  • Recognizes flags:
    • MY_FAE: terminates the program on error
      • MY_WME: write an error message
    • MY_ZEROFILL: zero-initialize the requested memory
      • If not set, fills the requested memory if the TRASH_FREED_MEMORY compile define is enabled – with 0xA5 bytes (¥) on allocation and 0x8F (C1 SS3) on free.

(The keen-eyed will notice that a couple of bullet points are repeated from sf_malloc() if SAFEMALLOC is enabled. my_free(void*) has fixed this repeated 0x8F-filling issue, though.)

alloc_root(MEM_ROOT*, size_t) @ include/my_sys.h

MEM_ROOT functions (not instance methods) invokes my_malloc() & co. in blocks.

alloc_root() flags MY_WME for my_malloc(), but flags ME_FATAL instead of MY_FAE…? This function also fills the – wait, it’s the third time I’m repeating this!

MEM_ROOT is intended for collections that free all of its elements at once. This is why this no longer has a corresponding freeing function.

Query_arena::alloc<T = char>(size_t)

And last but not least, this templated instance method is a convenience that simply delegates to alloc_root() using its MEM_ROOT. THD is a noteworthy grandchild of Query_arena, which also exports it to C as thd_alloc(const THD*, size_t) @ include/mysql/service_thd_alloc.h.

C++ Allocators

C++ has an Allocator concept that enables allocation customization for its collection classes. The default allocator is std::allocator.

I wrote operator news, operator deletes and allocators for the specializations above as C++ Practice. I have not yet published this file nor pitched it to the organization, though, because I doubt its adoption.

Strings

TODO: Surprisingly, we don’t use C++’s std::string here.

Data Collections

Yep, if not even std::string, let alone “the STL structures”. For them, the differences look great summarized as tables.

Variable-Size Fixed-Capacity Array inplace_vector<T, …> (C++26) Bounds_checked_array @ sql/sql_array.h TODO
TODO: has no-exception and unchecked versions of insertion methods
Growing & Shrinking
Additional methods Iterator, data & underlying buffer methods, empty (!size()), front & back TODO TODO
Bounds Checking
Variable-size Variable-Capacity Array std::vector<T, …> Dynamic_array<Elem> @ sql/sql_array.h (C++ wrapper) DYNAMIC_ARRAY @ include/my_sys.h (C) MEM_ROOT_DYNAMIC_ARRAY @ include/my_sys.h (C)
Element type Any type argument – can be whole instances or non-owning pointers ← same (no emplace element construction) uchar* & size – somewhat inconvenient void* & size
Allocator Optionally specified when constructing same → my_malloc() alloc_root()
Load Factor 100% same → 100% N/A: no random insert/delete functions
Size per grow unspecified, commonly 100% Optionally specified when constructing; default → 16 elements Optionally specified when constructing; 0 → [[(8160 bytes ÷ bytes/element).truncate, 2× initial capacity].min, 16].max elements ← same (ignored if the MY_BUFFER_NO_RESIZE flag is set, where resizing get & set outputs NULL & true (instead of false) respectively)
Default capacity not configurable Optionally specified when constructing; default → 16 elements Specified when constructing ← same
Initial static buffer not configurable Optionally for the initial capacity using alloc_root() Specified non-NULL when constructing ← same
Additional methods Iterator, data & underlying buffer methods, empty (!size()), front & back, push_back & pop_back, resize elements(size_t) (resize), find_first, front & back, push & pop, sort (QuickSort), underlying buffer methods alloc_dynamic (reserve one more element) & underlying buffer functions, delete_dynamic_with_callback (clear with destructor), pop_dynamic & push_dynamic, sort_dynamic (Quicksort) resizing get/set, mem_root_allocate_dynamic (reserve), mem_root_dynamic_array_copy_values, mem_root_dynamic_array_resize_not_allowed (set the MY_BUFFER_NO_RESIZE flag)
Bounds Checking at: throws std::out_of_range; others: UB pop: outputs NULL; others: debug assertion fails get: outputs 0s; set: resize; pop: outputs NULL; others: OOB memory Debug assertion fails
Doubly Linked List std::list<T, …> TODO TODO
Allocator Optionally specified when constructing TODO TODO
Additional methods Iterator, empty (!size()), merge (for sorted lists), remove_if, resize, reverse, sort (≅Θ(N log N)), splice (move all of another list), unique (deduplicate) TODO TODO
Bounds Checking TODO TODO TODO
Singly linked version std::forward_list<T, …> TODO TODO
Sorted Tree Set/Map
C++ std::(multi_)set<Key, …>/map<Key, T, …>
MariaDB Not available, it seems – use bsearch() on an array.
Unordered Hash Set/Map std::(multi_)unordered_set<Key, …>/_map<Key, T, …> Hash_set<T> @ sql/sql_hset.h (C++ wrapper that’s seldom used) HASH @ include/hash.h (C)
Element type Any type argument – can be whole instances or non-owning pointers Pointer to any type argument uchar*s of a charset specified when constructing (not even void*) – very inconvenient
Element destruction Elements’ destructor (which does not destruct/free the data pointed by plain pointers) same → Specified when constructing; NULL → do nothing (does not free the pointed buffer)
Hash Maps Separate classes, with map classes having separate key/value types same → Emulate from a set of key-value pairs by configuring a my_hash_get_key() (default: &element[key_offset])
Multi-Set mode Separate classes same (default flagged) → HASH_UNIQUE flag not set
Allocator Optionally specified when constructing same → my_malloc()
Hash function Optionally specified when constructing; default → std::hash() same → Optionally specified when constructing; default or NULL → MY_COLLATION_HANDLER::hash_sort()
Key equality function Optionally specified when constructing; default → operator== same → MY_COLLATION_HANDLER::strnncoll()
Load Factor Configurable via max_load_factor(float); default → 100% same → Implemented with DYNAMIC_ARRAY, therefore 100%
Size per grow probablybucket_size() same → Optionally specified when constructing; default or 0 → 8160 bytes (enough for over 500 entries!)
Default capacity if unspecified when constructing unspecified 8 elements 0
Additional methods Iterator, empty (!size()), extract (get + delete), merge, underlying bucket methods Iterator, is_empty my_hash_iterate
Bounds Checking unordered_map::operator[]: new insertion; unordered_map::at: throws std::out_of_range; others: UB same → read: outputs NULL; delete: outputs true (instead of false)

C++ Standard Library Resources @ cppreference.com:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment