{"id":108547,"date":"2023-08-04T07:00:00","date_gmt":"2023-08-04T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108547"},"modified":"2023-08-04T06:39:24","modified_gmt":"2023-08-04T13:39:24","slug":"20230804-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230804-00\/?p=108547","title":{"rendered":"Inside STL: The lists"},"content":{"rendered":"<p>The C++ standard library type <code>list<\/code> represents a doubly-linked list, and <code>forward_list<\/code> is a singly-linked list. Fortunately, the implementations of both of these lists are pretty much what you expect.<\/p>\n<p>Let&#8217;s start with the simpler <code>forward_<wbr \/>list<\/code>.<\/p>\n<pre>template&lt;typename T&gt;\r\nstruct forward_list\r\n{\r\n    forward_list_node&lt;T&gt;* head;\r\n};\r\n\r\ntemplate&lt;typename T&gt;\r\nstruct forward_list_node\r\n{\r\n    forward_list_node&lt;T&gt;* next;\r\n    T value;\r\n};\r\n<\/pre>\n<p>The <code>forward_<wbr \/>list<\/code> itself is a pointer to the first element of the list, or <code>nullptr<\/code> if the list is empty. Each subsequent element contains a pointer to the next element, or <code>nullptr<\/code> if there is no next element.<\/p>\n<p>For example, a two-element list of integers 1 and 2 is laid out like this:<\/p>\n<div id=\"p20230804_head\" style=\"display: none;\">\u00a0<\/div>\n<table style=\"border-collapse: collapse; text-align: center;\" title=\"A forward_list object containing only a next pointer. That next pointer points to a node object whose value is 1 and whose next pointer points to another node object whose value is 2 and whose next pointer is null.\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td>forward_list<\/td>\n<td>&nbsp;<\/td>\n<td>node<\/td>\n<td>&nbsp;<\/td>\n<td>node<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px gray;\">next<\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\">next<\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\">next = null<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">value = 1<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">value = 2<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The doubly-linked list <code>list<\/code> is also pretty simple.<\/p>\n<pre>template&lt;typename T&gt;\r\nstruct list\r\n{\r\n    list_node_base&lt;T&gt; head; \/\/ or \"list_node_base&lt;T&gt;* head;\"\r\n    size_t size;\r\n};\r\n\r\ntemplate&lt;typename T&gt;\r\nstruct list_node_base\r\n{\r\n    list_node&lt;T&gt;* next;\r\n    list_node&lt;T&gt;* prev;\r\n};\r\n\r\ntemplate&lt;typename T&gt;\r\nstruct list_node : list_node_base&lt;T&gt;\r\n{\r\n    T value;\r\n};\r\n<\/pre>\n<p>The list is actually circular, with a sentinel node (that has no value) marking the beginning and end of the list. Again, there are two patterns, depending on whether the sentinel is embedded or external.<\/p>\n<p>Here&#8217;s the version with an embedded sentinel:<\/p>\n<table style=\"border-collapse: collapse; text-align: center;\" title=\"A list object with a size member whose value is 2, and two nodes. The list's next pointer points to the first node, whose next pointer points to the second node, whose next pointer points back to the list. The prev pointers link the nodes in reverse order, forming a doubly-linked circular list.\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>list<\/td>\n<td>&nbsp;<\/td>\n<td>node 1<\/td>\n<td>&nbsp;<\/td>\n<td>node 2<\/td>\n<\/tr>\n<tr>\n<td>node 2<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">head.next<\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\">next<\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\">next<\/td>\n<td>\u2192<\/td>\n<td>list.head<\/td>\n<\/tr>\n<tr style=\"position: relative;\">\n<td style=\"font-size: 60%;\">(wraparound)<\/td>\n<td style=\"position: relative; top: -1ex;\">\ufe0e\u2196<\/td>\n<td style=\"border: solid 1px gray;\">head.prev<\/td>\n<td style=\"position: relative; top: -1ex;\">\ufe0e\u2196<\/td>\n<td style=\"border: solid 1px gray;\">prev<\/td>\n<td style=\"position: relative; top: -1ex;\">\ufe0e\u2196<\/td>\n<td style=\"border: solid 1px gray;\">prev<\/td>\n<td>&nbsp;<\/td>\n<td style=\"font-size: 60%;\">(wraparound)<\/td>\n<\/tr>\n<tr style=\"position: relative;\">\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">size = 2<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">value = 1<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">value = 2<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>And here&#8217;s the version with an external sentinel:<\/p>\n<table style=\"border-collapse: collapse; text-align: center;\" title=\"A double-linked circular list of a sentinel object and two node objects. A separate list object has a head pointer which points to the sentinel.\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td>list<\/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<\/tr>\n<tr style=\"position: relative;\">\n<td style=\"border: solid 1px gray;\">head<\/td>\n<td>\u2192<\/td>\n<td style=\"position: relative; left: -1ex; top: 1.5ex;\" align=\"left\">\u2198<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px gray;\">size = 2<\/td>\n<td>&nbsp;<\/td>\n<td align=\"left\">\u2193<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>sentinel<\/td>\n<td>&nbsp;<\/td>\n<td>node 1<\/td>\n<td>&nbsp;<\/td>\n<td>node 2<\/td>\n<\/tr>\n<tr>\n<td>node 2<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">next<\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\">next<\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\">next<\/td>\n<td>\u2192<\/td>\n<td>sentinel<\/td>\n<\/tr>\n<tr style=\"position: relative;\">\n<td style=\"font-size: 60%;\">(wraparound)<\/td>\n<td style=\"position: relative; top: -1ex;\">\ufe0e\u2196<\/td>\n<td style=\"border: solid 1px gray;\">prev<\/td>\n<td style=\"position: relative; top: -1ex;\">\ufe0e\u2196<\/td>\n<td style=\"border: solid 1px gray;\">prev<\/td>\n<td style=\"position: relative; top: -1ex;\">\ufe0e\u2196<\/td>\n<td style=\"border: solid 1px gray;\">prev<\/td>\n<td>&nbsp;<\/td>\n<td style=\"font-size: 60%;\">(wraparound)<\/td>\n<\/tr>\n<tr style=\"position: relative;\">\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">value = 1<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\">value = 2<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The Microsoft implementation uses an external sentinel, whereas clang and gcc use an embedded sentinel. This means that the Microsoft implementation incurs an extra allocation for empty lists. This is a minor annoyance because you have to worry about <code>std::<wbr \/>bad_alloc<\/code> exceptions at construction.<\/p>\n<p>The only complication is the usual one: Storing the allocator, which is done as a compressed pair with the <code>head<\/code>.<\/p>\n<p>The Visual Studio debugger contains a visualizer for both <code>forward_<wbr \/>list<\/code> and <code>list<\/code> 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.<\/p>\n<p>First, the forward list:<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; ?? l\r\nclass std::forward_list&lt;int,<wbr \/>std::allocator&lt;int&gt; &gt;\r\n   +0x000 _Mypair          : std::_Compressed_pair&lt;<wbr \/>std::allocator&lt;<wbr \/>std::_Flist_node&lt;<wbr \/>int,<wbr \/>void *&gt; &gt;,<wbr \/>std::_Flist_val&lt;<wbr \/>std::_Flist_simple_types&lt;<wbr \/>int&gt; &gt;,<wbr \/>1&gt;\r\n0:000&gt; ?? l._Mypair\r\nclass std::_Compressed_pair&lt;<wbr \/>std::allocator&lt;<wbr \/>std::_Flist_node&lt;<wbr \/>int,<wbr \/>void *&gt; &gt;,<wbr \/>std::_Flist_val&lt;<wbr \/>std::_Flist_simple_types&lt;<wbr \/>int&gt; &gt;,<wbr \/>1&gt;\r\n   +0x000 _Myval2          : std::_Flist_val&lt;<wbr \/>std::_Flist_simple_types&lt;<wbr \/>int&gt; &gt;\r\n0:000&gt; ?? l._Mypair._Myval2\r\nclass std::_Flist_val&lt;<wbr \/>std::_Flist_simple_types&lt;<wbr \/>int&gt; &gt;\r\n   +0x000 _Myhead          : 0x000001b8`7b6106a0 std::_Flist_node&lt;<wbr \/>int,<wbr \/>void *&gt;\r\n0:000&gt; ?? l._Mypair._Myval2._Myhead\r\nstruct std::_Flist_node&lt;<wbr \/>int,<wbr \/>void *&gt; * 0x000001b8`7b6106a0\r\n   +0x000 _Next            : 0x000001b8`7b6106e0 std::_Flist_node&lt;<wbr \/>int,<wbr \/>void *&gt;\r\n   +0x008 _Myval           : 0n42\r\n0:000&gt; ?? l._Mypair._Myval2._Myhead-&gt;_Next\r\nstruct std::_Flist_node&lt;<wbr \/>int,<wbr \/>void *&gt; * 0x000001b8`7b6106e0\r\n   +0x000 _Next            : (null)\r\n   +0x008 _Myval           : 0n99\r\n<\/pre>\n<p>We have to dig through a bunch of wrappers until we get to the <code>_Myhead<\/code>, but then it&#8217;s smooth sailing dumping each node and following the <code>_Next<\/code> to the next node.<\/p>\n<p>The layout of the <code>forward_list<\/code> nodes happens to match the Windows NT <code>SINGLE_<wbr \/>LIST_<wbr \/>ENTRY<\/code> structures, so you can use the debugger list commands to view them.<\/p>\n<pre style=\"white-space: pre-wrap;\">          head                max size\r\n          \u2193                   \u2193   \u2193\r\n0:000&gt; dl 0x000001b8`7b6106a0 999 2\r\n000001b8`7b6106a0  000001b8`7b6106e0 baadf00d`0000002a\r\n000001b8`7b6106e0  00000000`00000000 baadf00d`00000063\r\n\u2191                  \u2191                 \u2191        \u2191\r\nnode address       _Next             padding  _Myval\r\n<\/pre>\n<p>We ask the debugger&#8217;s &#8220;dump list&#8221; command (<code>dl<\/code>) to dump the linked list starting at our <code>head<\/code> (<code>0x000001b8`7b6106a0<\/code>), for a maximum of 999 nodes, where each node consists of two pointer-sized values. From that, we can quickly pull out the values <code>0x2a<\/code> (42) and <code>0x63<\/code> (99). If you are willing to be sloppy in the service of expediency, you could just dump the list object itself with <code>dl<\/code>, with the understanding that the first entry is going to be garbage. (It&#8217;s trying to dump the list itself as a node.)<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; ?? &amp;l\r\nclass std::forward_list&lt;int,<wbr \/>std::allocator&lt;int&gt; &gt; * 0000009c`379df8c0\r\n0:000&gt; dl 0x0000009c`379df8c0 999 2\r\n0000009c`379df8c0  000001b8`7b6106a0 0x000063`0000002a \u2190 garbage first \"node\"\r\n000001b8`7b6106a0  000001b8`7b6106e0 baadf00d`0000002a\r\n000001b8`7b6106e0  00000000`00000000 baadf00d`00000063\r\n<\/pre>\n<p>You can do a little flex and do it all in one line:<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; dl @@c++(&amp;l) 999 2\r\n0000009c`379df8c0  000001b8`7b6106a0 0x000063`0000002a \u2190 garbage first \"node\"\r\n000001b8`7b6106a0  000001b8`7b6106e0 baadf00d`0000002a\r\n000001b8`7b6106e0  00000000`00000000 baadf00d`00000063\r\n<\/pre>\n<p>The <code>@@c++(...)<\/code> notation tells the debugger that the enclosed expression is a C++ expression instead of a MASM expression.<\/p>\n<p>The <code>dl<\/code> command stops when it reaches the maximum number of nodes, it encounters a null pointer, or the list loops back to the start.<\/p>\n<p>If you really want to get fancy, you can use the <code>!list<\/code> command, which lets you customize even further how the elements are displayed.<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; !list -t scratch!LIST_ENTRY.Flink -x \"? dwo(@$extret+8)\" 0000009c`379df8c0\r\nEvaluate expression: 42 = 00000000`0000002a \u2190 garbage first \"node\"\r\n\r\nEvaluate expression: 42 = 00000000`0000002a\r\n\r\nEvaluate expression: 99 = 00000000`00000063\r\n<\/pre>\n<p>Dumping a <code>list<\/code> has a similar initial annoyance, followed by straightforward pointer-chasing. The only trick is knowing when to stop!<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; ?? l\r\nclass std::list&lt;<wbr \/>int,<wbr \/>std::allocator&lt;<wbr \/>int&gt; &gt;\r\n   +0x000 _Mypair          : std::_Compressed_pair&lt;<wbr \/>std::allocator&lt;<wbr \/>std::_List_node&lt;<wbr \/>int,<wbr \/>void *&gt; &gt;,<wbr \/>std::_List_val&lt;<wbr \/>std::_List_simple_types&lt;<wbr \/>int&gt; &gt;,<wbr \/>1&gt;\r\n0:000&gt; ?? l._Mypair\r\nclass std::_Compressed_pair&lt;<wbr \/>std::allocator&lt;<wbr \/>std::_List_node&lt;<wbr \/>int,<wbr \/>void *&gt; &gt;,<wbr \/>std::_List_val&lt;<wbr \/>std::_List_simple_types&lt;<wbr \/>int&gt; &gt;,<wbr \/>1&gt;\r\n   +0x000 _Myval2          : std::_List_val&lt;<wbr \/>std::_List_simple_types&lt;<wbr \/>int&gt; &gt;\r\n0:000&gt; ?? l._Mypair._Myval2\r\nclass std::_List_val&lt;<wbr \/>std::_List_simple_types&lt;<wbr \/>int&gt; &gt;\r\n   +0x000 _Myhead          : 0x0000017f`a63cf020 std::_List_node&lt;<wbr \/>int,<wbr \/>void *&gt;\r\n   +0x008 _Mysize          : 2\r\n0:000&gt; ?? l._Mypair._Myval2._Myhead\r\nstruct std::_List_node&lt;<wbr \/>int,<wbr \/>void *&gt; * 0x0000017f`a63cf020\r\n   +0x000 _Next            : 0x0000017f`a63d2730 std::_List_node&lt;<wbr \/>int,<wbr \/>void *&gt;\r\n   +0x008 _Prev            : 0x0000017f`a63d2780 std::_List_node&lt;<wbr \/>int,<wbr \/>void *&gt;\r\n   +0x010 _Myval           : 0n-1163005939\r\n0:000&gt; ?? l._Mypair._Myval2._Myhead-&gt;_Next\r\nstruct std::_List_node&lt;<wbr \/>int,<wbr \/>void *&gt; * 0x0000017f`a63d2730\r\n   +0x000 _Next            : 0x0000017f`a63d2780 std::_List_node&lt;<wbr \/>int,<wbr \/>void *&gt;\r\n   +0x008 _Prev            : 0x0000017f`a63cf020 std::_List_node&lt;<wbr \/>int,<wbr \/>void *&gt;\r\n   +0x010 _Myval           : 0n42\r\n0:000&gt; ?? l._Mypair._Myval2._Myhead-&gt;_Next-&gt;_Next\r\nstruct std::_List_node&lt;<wbr \/>int,<wbr \/>void *&gt; * 0x0000017f`a63d2780\r\n   +0x000 _Next            : 0x0000017f`a63cf020 std::_List_node&lt;<wbr \/>int,<wbr \/>void *&gt;\r\n   +0x008 _Prev            : 0x0000017f`a63d2730 std::_List_node&lt;<wbr \/>int,<wbr \/>void *&gt;\r\n   +0x010 _Myval           : 0n99\r\n<\/pre>\n<p>At this point, we stop because the <code>_Next<\/code> value matches our sentinel value. If we didn&#8217;t recognize this, we would just follow the <code>_Next<\/code> pointer which takes us back to the sentinel:<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; ?? l._Mypair._Myval2._Myhead-&gt;_Next-&gt;_Next-&gt;_Next\r\nstruct std::_List_node&lt;<wbr \/>int,<wbr \/>void *&gt; * 0x0000017f`a63cf020\r\n   +0x000 _Next            : 0x0000017f`a63d2730 std::_List_node&lt;<wbr \/>int,<wbr \/>void *&gt;\r\n   +0x008 _Prev            : 0x0000017f`a63d2780 std::_List_node&lt;<wbr \/>int,<wbr \/>void *&gt;\r\n   +0x010 _Myval           : 0n-1163005939\r\n<\/pre>\n<p>The layout of the <code>list<\/code> matches the Windows NT <code>LIST_<wbr \/>ENTRY<\/code>, so the <code>dl<\/code> command can once again be pressed into service.<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; dl 0x0000017f`a63cf020 999 3\r\n0000017f`a63cf020  0000017f`a63d2730 0000017f`a63d2780\r\n0000017f`a63cf030  baadf00d`baadf00d \u2190 garbage value in sentinel\r\n0000017f`a63d2730  0000017f`a63d2780 0000017f`a63cf020\r\n0000017f`a63d2740  baadf00d`0000002a \u2190 value 42\r\n0000017f`a63d2780  0000017f`a63cf020 0000017f`a63d2730\r\n0000017f`a63d2790  baadf00d`00000063 \u2190 value 99\r\n<\/pre>\n<p>The nodes are a little harder to read since they spill over two lines. The first line of each node shows the <code>_Next<\/code> and <code>_Prev<\/code> pointers. The second line of each node shows the value.<\/p>\n<p>As a parlor trick, you can use the <code>dlb<\/code> command to dump the doubly-linked list <i>backward<\/i>.<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; dlb 0x0000017f`a63cf020 999 3\r\n0000017f`a63cf020  0000017f`a63d2730 0000017f`a63d2780\r\n0000017f`a63cf030  baadf00d`baadf00d \u2190 garbage value in sentinel\r\n0000017f`a63d2780  0000017f`a63cf020 0000017f`a63d2730\r\n0000017f`a63d2790  baadf00d`00000063 \u2190 value 99\r\n0000017f`a63d2730  0000017f`a63d2780 0000017f`a63cf020\r\n0000017f`a63d2740  baadf00d`0000002a \u2190 value 42\r\n<\/pre>\n<p>Since the nodes are so clumsy to read, this is a case where the <code>!list<\/code> command comes in handy.<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; !list -t scratch!LIST_ENTRY.Flink -x \"? dwo(@$extret+0x10)\" 0000009c`379df8c0\r\nEvaluate expression: 3131961357 = 00000000`baadf00d \u2190 garbage value in sentinel\r\n\r\nEvaluate expression: 42 = 00000000`0000002a\r\n<\/pre>\n<p>I&#8217;m not sure why the debugger gives up just before reaching the last node.<\/p>\n<p>Okay, so the <code>list<\/code> and <code>forward_<wbr \/>list<\/code> aren&#8217;t particularly tricky, but we&#8217;ll need them later. Stay tuned.<\/p>\n<p>\n<script>\nwindow.addEventListener(\"load\", function() {\n  var fullFF = getComputedStyle(document.body).fontFamily;\n  var simpleFF = fullFF.replace(\/ Emoji\/g, \"\");\n  \/\/ break up \"style\" to prevent wordpress from injecting random junk\n  document.getElementById(\"p20230804_head\").innerHTML =\n`<s` + `tyle>\nbody { font-family: ${simpleFF}; }\n.emoji { font-family: ${fullFF}; }\n.entry-content th { padding: 1px; } \/* stylesheet workaround *\/\n.entry-content td { padding: 1px; } \/* stylesheet workaround *\/\n<\/s` + `tyle>`;\n}); \/\/ wacky comment to prevent wordpress from injecting random junk\n<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Your traditional singly- or doubly-linked list.<\/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-108547","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Your traditional singly- or doubly-linked list.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108547","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=108547"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108547\/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=108547"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108547"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108547"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}