{"id":108562,"date":"2023-08-07T07:00:00","date_gmt":"2023-08-07T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108562"},"modified":"2023-08-07T07:35:37","modified_gmt":"2023-08-07T14:35:37","slug":"20230807-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230807-00\/?p=108562","title":{"rendered":"Inside STL: The map, set, multimap, and multiset"},"content":{"rendered":"<p>The complexity requirements of the C+ standard library <code>map<\/code>, <code>set<\/code>, <code>multimap<\/code>, and <code>multiset<\/code> basically narrow the possible implementation options to AVL trees and red-black trees.<\/p>\n<p>In practice, everybody seems to choose a red-black tree, with an explicit sentinel node. The sentinel node also doubles as a header. The structure of the tree is as follows:<\/p>\n<pre>struct tree\r\n{\r\n    node_base header; \/\/ or \"node_base* header;\"\r\n    size_t size;\r\n};\r\n\r\nstruct node_base\r\n{\r\n    node_base* parent;\r\n    node_base* left;\r\n    node_base* right;\r\n};\r\n\r\nstruct node : node_base\r\n{\r\n    bool color; \/\/ red or black\r\n    payload data;\r\n};\r\n<\/pre>\n<p>The tree remembers the number of elements it holds (because <code>size()<\/code> is required to be <var>O<\/var>(1)) and has a sentinel node, called the &#8220;header&#8221;. The sentinel node is either embedded in the tree object or is a separately-allocated node, depending on the implementation.<\/p>\n<p>The sentinel node uses its pointers in an unusual manner.<\/p>\n<ul>\n<li><code>parent<\/code> points to the root of the tree.<\/li>\n<li><code>left<\/code> points to the smallest item in the tree.<\/li>\n<li><code>right<\/code> points to the largest item in the tree.<\/li>\n<\/ul>\n<p>If you follow the <code>header.<wbr \/>parent<\/code> to the root of the tree, then what you find is a standard red-black binary tree, with the <code>left<\/code> node pointing to the subtree of smaller elements, the <code>right<\/code> node pointing to the subtree of larger elements, and the <code>parent<\/code> node pointing to the parent element.<\/p>\n<p>The header node is used to represent <code>end()<\/code>, the one-past-the-end iterator.<\/p>\n<p>For a <code>map<\/code> or <code>multimap<\/code>, the payload is a <code>std::<wbr \/>pair&lt;<wbr \/>Key, T&gt;<\/code>. For a <code>set<\/code> or <code>multiset<\/code>, the payload is a <code>Key<\/code>.<\/p>\n<p>Here&#8217;s an example of how a <code>map<\/code> is laid out in memory:<\/p>\n<pre>std::map&lt;int, std::string&gt; m = {\r\n    { 42, \"forty-two\" },\r\n    { 99, \"ninety-nine\" },\r\n};\r\n<\/pre>\n<p>First, with an internal header node:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse; text-align: center;\" title=\"Diagram of memory layout (see text)\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td colspan=\"2\">m<\/td>\n<td>&nbsp;<\/td>\n<td colspan=\"2\">A<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px gray;\">header.parent<\/td>\n<td style=\"border: solid 1px gray;\">A<\/td>\n<td>\u2194<\/td>\n<td style=\"border: solid 1px gray;\">parent<\/td>\n<td style=\"border: solid 1px gray;\">m.header<\/td>\n<td>&nbsp;<\/td>\n<td colspan=\"2\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px gray;\">header.left<\/td>\n<td style=\"border: solid 1px gray;\">A<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">left<\/td>\n<td style=\"border: solid 1px gray;\">null<\/td>\n<td>&nbsp;<\/td>\n<td colspan=\"2\">B<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px gray;\">header.right<\/td>\n<td style=\"border: solid 1px gray;\">B<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">right<\/td>\n<td style=\"border: solid 1px gray;\">B<\/td>\n<td>\u2194<\/td>\n<td style=\"border: solid 1px gray;\">parent<\/td>\n<td style=\"border: solid 1px gray;\">A<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px gray;\">size<\/td>\n<td style=\"border: solid 1px gray;\">2<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">color<\/td>\n<td style=\"border: solid 1px gray;\">black<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">left<\/td>\n<td style=\"border: solid 1px gray;\">null<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">data<\/td>\n<td style=\"border: solid 1px gray;\">42, <code>\"forty-two\"<\/code><\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">right<\/td>\n<td style=\"border: solid 1px gray;\">null<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">color<\/td>\n<td style=\"border: solid 1px gray;\">red<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">data<\/td>\n<td style=\"border: solid 1px gray;\">99, <code>\"ninety-nine\"<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>And again with an external header node:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse; text-align: center;\" title=\"Diagram of memory layout (see text)\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td colspan=\"2\">m<\/td>\n<td>&nbsp;<\/td>\n<td colspan=\"2\">H<\/td>\n<td>&nbsp;<\/td>\n<td colspan=\"2\">A<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px gray;\">header<\/td>\n<td style=\"border: solid 1px gray;\">H<\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\">parent<\/td>\n<td style=\"border: solid 1px gray;\">A<\/td>\n<td>\u2194<\/td>\n<td style=\"border: solid 1px gray;\">parent<\/td>\n<td style=\"border: solid 1px gray;\">H<\/td>\n<td>&nbsp;<\/td>\n<td colspan=\"2\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px gray;\">size<\/td>\n<td style=\"border: solid 1px gray;\">2<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">left<\/td>\n<td style=\"border: solid 1px gray;\">A<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">left<\/td>\n<td style=\"border: solid 1px gray;\">null<\/td>\n<td>&nbsp;<\/td>\n<td colspan=\"2\">B<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">right<\/td>\n<td style=\"border: solid 1px gray;\">B<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">right<\/td>\n<td style=\"border: solid 1px gray;\">B<\/td>\n<td>\u2194<\/td>\n<td style=\"border: solid 1px gray;\">parent<\/td>\n<td style=\"border: solid 1px gray;\">A<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">color<\/td>\n<td style=\"border: solid 1px gray;\">black<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">left<\/td>\n<td style=\"border: solid 1px gray;\">null<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">data<\/td>\n<td style=\"border: solid 1px gray;\">42, <code>\"forty-two\"<\/code><\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">right<\/td>\n<td style=\"border: solid 1px gray;\">null<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">color<\/td>\n<td style=\"border: solid 1px gray;\">red<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">data<\/td>\n<td style=\"border: solid 1px gray;\">99, <code>\"ninety-nine\"<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The map object itself has two members: The <code>header<\/code> is (or points to) our sentinel\/header node <code>H<\/code>, and the <code>size<\/code> is 2, remembering that we have two nodes in our tree.<\/p>\n<p>The header node&#8217;s <code>parent<\/code> points to the root of the tree <code>A<\/code>. This is confusing at first, because the member named &#8220;parent&#8221; actually points to the &#8220;child&#8221;, if you think of the header node as the &#8220;super-parent&#8221; of the entire tree. The header node&#8217;s <code>left<\/code> and <code>right<\/code> members point to the smallest and largest nodes of the tree, respectively. In our case, the smallest node is <code>A<\/code> (42) and the largest node is <code>B<\/code> (99). Finally, the <code>color<\/code> and <code>data<\/code> of the header node are not used.<\/p>\n<p>The root of our tree is the node <code>A<\/code>. It remembers that its parent is the header node <code>H<\/code>, but the interesting parts are the <code>left<\/code> and <code>right<\/code> pointers. There is no left subtree, so the <code>left<\/code> is null; the right subtree is rooted at <code>B<\/code>. Finally, the node also has a <code>color<\/code> (black in this case), and its <code>data<\/code> holds the payload.<\/p>\n<p>The last node in the tree is <code>B<\/code>, representing the single-node right subtree of <code>A<\/code>. Its parent is <code>A<\/code>, and its <code>left<\/code> and <code>right<\/code> nodes are null because it is a leaf node. Node <code>B<\/code> also has a <code>color<\/code> (red in this case), and its <code>data<\/code> holds the payload.<\/p>\n<p>The tree also needs a comparer and allocator. Those classes are typically empty, so the tree saves them as compressed pairs with the rest of the data. (Since there are two usually-empty things, you have a compressed pair inside a compressed pair.)<\/p>\n<p>The Microsoft implementation uses an external header sentinel node, whereas clang and gcc use an internal header sentinel node. The Microsoft implementation also has these extra quirks:<\/p>\n<ul>\n<li>There is an additional member of each node called <code>_Isnil<\/code> that is <code>true<\/code> for the header node and that is <code>false<\/code> for everybody else.<\/li>\n<li>Everywhere you see null in the above diagram, the Microsoft implementation substitutes a pointer to the header node.<\/li>\n<\/ul>\n<p>The <code>_Isnil<\/code> member is technically redundant: You can detect that you have the header node by comparing it to the map&#8217;s header. However, the Visual C++ compiler uses a raw node pointer as its iterator, which means that it doesn&#8217;t have access to the containing tree to compare against the header. The information has to be internal to the node.<\/p>\n<p>The Visual Studio debugger contains a visualizer to view the contents of a tree-based container more conveniently, but if you need to dig out the contents manually, here&#8217;s how you can do it with the Microsoft implementation of the standard library. I&#8217;ve left in the template explosion to make it look more like what you&#8217;re likely to encounter in the debugger.<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; ?? m\r\nclass std::map&lt;int,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt;,std::less&lt;int&gt;,std::allocator&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt; &gt; &gt;\r\n   +0x000 _Mypair          : std::_Compressed_pair&lt;std::less&lt;int&gt;,std::_Compressed_pair&lt;std::allocator&lt;std::_Tree_node&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;,void *&gt; &gt;,std::_Tree_val&lt;std::_Tree_simple_types&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt; &gt; &gt;,1&gt;,1&gt;\r\n<\/pre>\n<p>The full name for the <code>std::<wbr \/>map&lt;int, std::<wbr \/>string&gt;<\/code> is this horrible monstrosity because <code>std::<wbr \/>string<\/code> is an alias for <code>std::<wbr \/>basic_<wbr \/>string&lt;<wbr \/>char&gt;<\/code>, which is in turn a shorthand for <code>std::<wbr \/>basic_<wbr \/>string&lt;<wbr \/>char, std::char_traits&lt;<wbr \/>char&gt;, std::<wbr \/>allocator&lt;char&gt;&gt;<\/code>. So everywhere you see the huge type <code>std::<wbr \/>basic_<wbr \/>string&lt;<wbr \/>char, std::char_traits&lt;<wbr \/>char&gt;, std::<wbr \/>allocator&lt;char&gt;&gt;<\/code>, remember that&#8217;s just a <code>std::<wbr \/>string<\/code>.<\/p>\n<p>On top of that, the <code>std::<wbr \/>map<\/code> itself comes with a default comparer of <code>std::<wbr \/>less&lt;<wbr \/>int&gt;<\/code> and a default allocator of <code>std::<wbr \/>allocator&lt;<wbr \/>std::<wbr \/>pair&lt;int, std::<wbr \/>string&gt;&gt;<\/code>. But wait, <code>std::<wbr \/>string<\/code> is itself an alias for that huge type name, so the default allocator&#8217;s full name is <code>std::<wbr \/>allocator&lt;<wbr \/>std::<wbr \/>pair&lt;int, std::<wbr \/>basic_<wbr \/>string&lt;<wbr \/>char, std::char_traits&lt;<wbr \/>char&gt;, std::<wbr \/>allocator&lt;char&gt;&gt;&gt;&gt;<\/code>. And that&#8217;s why when you declare a <code>std::<wbr \/>map&lt;int, std::<wbr \/>string&gt;<\/code>, you get this ridiculous type name in the debugger. That&#8217;s the name the compiler has to deal with.<\/p>\n<p>Okay, so we have the map. We need to dig through the compressed pairs to find the header pointer and size.<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; ?? m._Mypair\r\nclass std::_Compressed_pair&lt;std::less&lt;int&gt;,std::_Compressed_pair&lt;std::allocator&lt;std::_Tree_node&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;,void *&gt; &gt;,std::_Tree_val&lt;std::_Tree_simple_types&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt; &gt; &gt;,1&gt;,1&gt;\r\n   +0x000 _Myval2          : std::_Compressed_pair&lt;std::allocator&lt;std::_Tree_node&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;,void *&gt; &gt;,std::_Tree_val&lt;std::_Tree_simple_types&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt; &gt; &gt;,1&gt;\r\n0:000&gt; ?? m._Mypair._Myval2\r\nclass std::_Compressed_pair&lt;std::allocator&lt;std::_Tree_node&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;,void *&gt; &gt;,std::_Tree_val&lt;std::_Tree_simple_types&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt; &gt; &gt;,1&gt;\r\n   +0x000 _Myval2          : std::_Tree_val&lt;std::_Tree_simple_types&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt; &gt; &gt;\r\n0:000&gt; ?? m._Mypair._Myval2._Myval2\r\nclass std::_Tree_val&lt;std::_Tree_simple_types&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt; &gt; &gt;\r\n   +0x000 _Myhead          : 0x0000019a`d09cc720 std::_Tree_node&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;,void *&gt;\r\n   +0x008 _Mysize          : 2\r\n<\/pre>\n<p>We follow the header pointer to find the header node.<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; ?? m._Mypair._Myval2._Myval2._Myhead\r\nstruct std::_Tree_node&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;,void *&gt; * 0x0000019a`d09cc720\r\n   +0x000 _Left            : 0x0000019a`d09c91f0 std::_Tree_node&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;,void *&gt;\r\n   +0x008 _Parent          : 0x0000019a`d09c91f0 std::_Tree_node&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;,void *&gt;\r\n   +0x010 _Right           : 0x0000019a`d09c9270 std::_Tree_node&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;,void *&gt;\r\n   +0x018 _Color           : 1 ''\r\n   +0x019 _Isnil           : 1 ''\r\n   +0x020 _Myval           : std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;\r\n<\/pre>\n<p>We can confirm that we&#8217;re on the right track because the <code>_Isnil<\/code> is <code>true<\/code>, which is the case only for the header node. Follow the <code>_Parent<\/code> to get to the root of the tree.<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; ?? m._Mypair._Myval2._Myval2._Myhead-&gt;_Parent\r\nstruct std::_Tree_node&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;,void *&gt; * 0x0000019a`d09c91f0\r\n   +0x000 _Left            : 0x0000019a`d09cc720 std::_Tree_node&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;,void *&gt;\r\n   +0x008 _Parent          : 0x0000019a`d09cc720 std::_Tree_node&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;,void *&gt;\r\n   +0x010 _Right           : 0x0000019a`d09c9270 std::_Tree_node&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;,void *&gt;\r\n   +0x018 _Color           : 1 ''\r\n   +0x019 _Isnil           : 0 ''\r\n   +0x020 _Myval           : std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;\r\n<\/pre>\n<p>The payload of the root node is hiding inside the <code>_Myval<\/code> pair, so we&#8217;ll have to expand it out:<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; ?? m._Mypair._Myval2._Myval2._Myhead-&gt;_Parent-&gt;_Myval\r\nstruct std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;\r\n   +0x000 first            : 0n42\r\n   +0x008 second           : std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt;\r\n<\/pre>\n<p>We see immediately that the key of the root node is 42. We&#8217;re lucky that our map is keyed by <code>int<\/code>, which is a simple type. If not, we&#8217;d have had to dig into the members of the <code>first<\/code> to figure out what it is.<\/p>\n<p>Let&#8217;s demonstrate that by digging into the <code>second<\/code> of the pair, which holds the mapped value.<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; ?? m._Mypair._Myval2._Myval2._Myhead-&gt;_Parent-&gt;_Myval.second\r\nclass std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt;\r\n   +0x000 _Mypair          : std::_Compressed_pair&lt;std::allocator&lt;char&gt;,std::_String_val&lt;std::_Simple_types&lt;char&gt; &gt;,1&gt;\r\n0:000&gt; ?? m._Mypair._Myval2._Myval2._Myhead-&gt;_Parent-&gt;_Myval.second._Mypair\r\nclass std::_Compressed_pair&lt;std::allocator&lt;char&gt;,std::_String_val&lt;std::_Simple_types&lt;char&gt; &gt;,1&gt;\r\n   +0x000 _Myval2          : std::_String_val&lt;std::_Simple_types&lt;char&gt; &gt;\r\n0:000&gt; ?? m._Mypair._Myval2._Myval2._Myhead-&gt;_Parent-&gt;_Myval.second._Mypair._Myval2\r\nclass std::_String_val&lt;std::_Simple_types&lt;char&gt; &gt;\r\n   +0x000 _Bx              : std::_String_val&lt;std::_Simple_types&lt;char&gt; &gt;::_Bxty\r\n   +0x010 _Mysize          : 9\r\n   +0x018 _Myres           : 0xf\r\n0:000&gt; ?? m._Mypair._Myval2._Myval2._Myhead-&gt;_Parent-&gt;_Myval.second._Mypair._Myval2._Bx\r\nunion std::_String_val&lt;std::_Simple_types&lt;char&gt; &gt;::_Bxty\r\n   +0x000 _Buf             : [16]  \"forty-two\"\r\n   +0x000 _Ptr             : 0x77742d79`74726f66  \"--- memory read error at address 0x77742d79`74726f66 ---\"\r\n   +0x000 _Alias           : [16]  \"forty-two\"\r\n<\/pre>\n<p>In this case, the mapped type is a <code>std::<wbr \/>string<\/code>, so we use the string-dumping technique we learned about some time ago.<\/p>\n<p>So we&#8217;ve found that the root of the tree is <code>{ 42, \"forty-two\" }<\/code>. The <code>_Left<\/code> member points back to the header, so there is no left subtree. The <code>_Right<\/code> member holds some other pointer value, so we have a right subtree. Let&#8217;s explore it.<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; ?? m._Mypair._Myval2._Myval2._Myhead-&gt;_Parent-&gt;_Right\r\nstruct std::_Tree_node&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;,void *&gt; * 0x0000019a`d09c9270\r\n   +0x000 _Left            : 0x0000019a`d09cc720 std::_Tree_node&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;,void *&gt;\r\n   +0x008 _Parent          : 0x0000019a`d09c91f0 std::_Tree_node&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;,void *&gt;\r\n   +0x010 _Right           : 0x0000019a`d09cc720 std::_Tree_node&lt;std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;,void *&gt;\r\n   +0x018 _Color           : 0 ''\r\n   +0x019 _Isnil           : 0 ''\r\n   +0x020 _Myval           : std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;\r\n0:000&gt; ?? t._Mypair._Myval2._Myval2._Myhead-&gt;_Parent-&gt;_Right-&gt;_Myval\r\nstruct std::pair&lt;int const ,std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;\r\n   +0x000 first            : 0n99\r\n   +0x008 second           : std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt;\r\n0:000&gt; ?? m._Mypair._Myval2._Myval2._Myhead-&gt;_Parent-&gt;_Right-&gt;_Myval.second._Mypair._Myval2._Bx\r\nunion std::_String_val&lt;std::_Simple_types&lt;char&gt; &gt;::_Bxty\r\n   +0x000 _Buf             : [16]  \"ninety-nine\"\r\n   +0x000 _Ptr             : 0x6e2d7974`656e696e  \"--- memory read error at address 0x6e2d7974`656e696e ---\"\r\n   +0x000 _Alias           : [16]  \"ninety-nine\"\r\n0:000&gt;\r\n<\/pre>\n<p>When I&#8217;m dumping tree structures in the debugger at this low level, I generally ignore all the type information because it&#8217;s so voluminous. I rely on the member variable names to tell me what I&#8217;m looking at. Here&#8217;s what the above debug session looks like to me:<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; ?? m\r\nclass std::map&lt;int,std::basic_string&lt;char, \u27e6 ... \u27e7 &gt; &gt;\r\n   +0x000 _Mypair       : \u27e6 ... \u27e7 \u27ea ugh, a wrapper \u27eb\r\n0:000&gt; ?? m._Mypair\r\nclass \u27e6 ... \u27e7\r\n   +0x000 _Myval2       : \u27e6 ... \u27e7 \u27ea ugh, another wrapper \u27eb\r\n0:000&gt; ?? m._Mypair._Myval2\r\nclass \u27e6 ... \u27e7\r\n   +0x000 _Myval2       : \u27e6 ... \u27e7 \u27ea ugh, another wrapper \u27eb\r\n0:000&gt; ?? m._Mypair._Myval2._Myval2\r\nclass \u27e6 ... \u27e7\r\n   +0x000 _Myhead       : 0x0000019a`d09cc720 \u27e6 ... \u27e7 \u27ea yay, the header, end in c720 \u27eb\r\n   +0x008 _Mysize       : 2\r\n0:000&gt; ?? m._Mypair._Myval2._Myval2._Myhead\r\nstruct \u27e6 ... \u27e7* 0x0000019a`d09cc720 \u27ea yup, c720, this is the header \u27eb\r\n   \u27e6 ... \u27e7\r\n   +0x008 _Parent       : 0x0000019a`d09c91f0 \u27e6 ... \u27e7 \u27ea here is the root node \u27eb\r\n   \u27e6 ... \u27e7\r\n<\/pre>\n<p>And then we inspect the root node:<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; ?? m._Mypair._Myval2._Myval2._Myhead-&gt;_Parent\r\nstruct \u27e6 ... \u27e7 * 0x0000019a`d09c91f0\r\n   +0x000 _Left         : 0x0000019a`d09cc720 \u27e6 ... \u27e7 \u27ea this is c720, so no left subtree \u27eb\r\n   \u27e6 ... \u27e7\r\n   +0x010 _Right        : 0x0000019a`d09c9270 \u27e6 ... \u27e7 \u27ea there is a right subtree \u27eb\r\n   \u27e6 ... \u27e7\r\n   +0x020 _Myval        : \u27e6 ... \u27e7\r\n0:000&gt; ?? m._Mypair._Myval2._Myval2._Myhead-&gt;_Parent-&gt;_Myval\r\nstruct \u27e6 ... \u27e7\r\n   +0x000 first         : 0n42 \u27ea the key is 42 \u27eb\r\n   +0x008 second        : \u27e6 ... \u27e7 \u27ea the mapped value is hiding here \u27eb\r\n0:000&gt; ?? m._Mypair._Myval2._Myval2._Myhead-&gt;_Parent-&gt;_Myval.second\r\nclass \u27e6 ... \u27e7\r\n   +0x000 _Mypair       : \u27e6 ... \u27e7 \u27ea ugh, a wrapper \u27eb\r\n0:000&gt; ?? m._Mypair._Myval2._Myval2._Myhead-&gt;_Parent-&gt;_Myval.second._Mypair\r\nclass \u27e6 ... \u27e7\r\n   +0x000 _Myval2       : \u27e6 ... \u27e7 \u27ea ugh, another wrapper \u27eb\r\n0:000&gt; ?? m._Mypair._Myval2._Myval2._Myhead-&gt;_Parent-&gt;_Myval.second._Mypair._Myval2\r\nclass \u27e6 ... \u27e7\r\n   +0x000 _Bx           : \u27e6 ... \u27e7 \u27ea okay, the string data is in here \u27eb\r\n   \u27e6 ... \u27e7\r\n0:000&gt; ?? m._Mypair._Myval2._Myval2._Myhead-&gt;_Parent-&gt;_Myval.second._Mypair._Myval2._Bx\r\nunion std::_String_val&lt;std::_Simple_types&lt;char&gt; &gt;::_Bxty\r\n   +0x000 _Buf          : [16]  \"forty-two\"\r\n   +0x000 _Ptr          : 0x77742d79`74726f66  \u27ea garbage, ignore \u27eb\r\n    \u27e6 ... \u27e7\r\n<\/pre>\n<p>Next we follow the right subtree.<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; ?? m._Mypair._Myval2._Myval2._Myhead-&gt;_Parent-&gt;_Right\r\nstruct \u27e6 ... \u27e7 * 0x0000019a`d09c9270\r\n   +0x000 _Left         : 0x0000019a`d09cc720 \u27e6 ... \u27e7 \u27ea this is c720, so no left subtree \u27eb\r\n   \u27e6 ... \u27e7\r\n   +0x010 _Right        : 0x0000019a`d09cc720 \u27e6 ... \u27e7 \u27ea this is c720, so no right subtree \u27eb\r\n   \u27e6 ... \u27e7\r\n   +0x020 _Myval        : \u27e6 ... \u27e7\r\n0:000&gt; ?? t._Mypair._Myval2._Myval2._Myhead-&gt;_Parent-&gt;_Right-&gt;_Myval\r\nstruct \u27e6 ... \u27e7\r\n   +0x000 first         : 0n99 \u27ea the key is 99 \u27eb\r\n   +0x008 second        : \u27e6 ... \u27e7 \u27ea I learned the path to the _Bx from last time \u27eb\r\n0:000&gt; ?? m._Mypair._Myval2._Myval2._Myhead-&gt;_Parent-&gt;_Right-&gt;_Myval.second._Mypair._Myval2._Bx\r\nunion \u27e6 ... \u27e7\r\n   +0x000 _Buf          : [16]  \"ninety-nine\"\r\n   +0x000 _Ptr          : 0x6e2d7974`656e696e  \u27e6 ... \u27e7 \u27ea garbage, ignore \u27eb\r\n   \u27e6 ... \u27e7\r\n<\/pre>\n<p>There, we walked the complete contents of a <code>std::<wbr \/>map<\/code> in the low-level debugger. It&#8217;s quite a hassle.<\/p>\n<p><b>Bonus chatter<\/b>: Iterating through the tree can be done by performing an <a title=\"Tree-walking algorithms: Incrementally performing an inorder walk of a binary tree\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200109-00\/?p=103309\"> incremental inorder walk of a binary tree<\/a>. Observe that this means that the <code>operator++<\/code> on a tree iterator is not constant-time. However, it is <i>amortized<\/i> constant time, because each edge in the tree is traversed twice (once outgoing and once returning), and the number of edges is equal to the number of nodes minus one. Therefore, the number of steps to traverse the entire tree is 2<var>n<\/var> \u2212 2, which is <var>O<\/var>(<var>n<\/var>), and therefore each individual <code>operator++<\/code> has amortized constant cost, <a href=\"https:\/\/timsong-cpp.github.io\/cppwp\/n4861\/iterator.requirements.general#13\"> as required by the standard<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A red-black tree.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-108562","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>A red-black tree.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108562","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=108562"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108562\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=108562"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108562"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108562"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}