{"id":108005,"date":"2023-04-03T07:00:00","date_gmt":"2023-04-03T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108005"},"modified":"2023-04-02T20:16:52","modified_gmt":"2023-04-03T03:16:52","slug":"20230403-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230403-00\/?p=108005","title":{"rendered":"C++17 creates a practical use of the backward array index operator"},"content":{"rendered":"<p>It is well-known that if <code>a<\/code> is a pointer or array and <code>i<\/code> is an integer, then <code>a[i]<\/code> and <code>i[a]<\/code> are equivalent in C and C++, resulting in hilarity like<\/p>\n<pre>void haha()\r\n{\r\n    int a[5];\r\n    for (i = 0; i &lt; 5; i++) {\r\n        i[a] = 42;\r\n    }\r\n}\r\n<\/pre>\n<p>There is very little practical use for this equivalency, aside from pranking people.\u00b9<\/p>\n<p>And then C++17 happened.<\/p>\n<p>One of the changes to the core language in C++17 was stronger order of evaluation rules, formally known as <i>sequencing<\/i>. We previously encountered this <a title=\"The mystery of the crash that seems to be on a std::move operation\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220120-00\/?p=106178\"> when studying a crash that seemed to be on a <code>std::move<\/code> operation<\/a>.<\/p>\n<p>One of the operations that received a defined order of evaluation is the subscript operator. Starting in C++17, <code>a[b]<\/code> always evaluates <code>a<\/code> before evaluating <code>b<\/code>.<\/p>\n<pre>int* p;\r\nint index();\r\n\r\nauto test()\r\n{\r\n    return p[index()];\r\n}\r\n\r\n\/\/ Compiled as C++14\r\n\r\n    sub     rsp, 40\r\n    call    index       ; call index first\r\n    movsxd  rcx, rax\r\n    mov     rax, p      ; then fetch p\r\n    mov     eax, [rax + rcx * 4]\r\n    add     rsp, 40\r\n    ret\r\n\r\n\/\/ Compiled as c++17\r\n\r\n    push    rbx\r\n    sub     rsp, 32\r\n    mov     rbx, p      ; fetch p first\r\n    call    index       ; then call index\r\n    movsxd  rcx, rax\r\n    mov     eax, [rbx + rcx * 4]\r\n    add     rsp, 32\r\n    pop     rbx\r\n    ret\r\n<\/pre>\n<p>Therefore, if your evaluation of the index may have a side effect on the evaluation of the pointer, you can flip the order to force the index to be calculated first.<\/p>\n<pre>auto test()\r\n{\r\n    return index()[p];\r\n}\r\n<\/pre>\n<p>Astound your friends! Confuse your enemies!<\/p>\n<p><b>Bonus chatter<\/b>: Though I wouldn&#8217;t rely on this yet. clang implements this correctly, but <a href=\"https:\/\/gcc.godbolt.org\/z\/s11KhnKcn\"> msvc (v19) and gcc (v13) get the order wrong<\/a> and still load <code>p<\/code> before calling <code>index<\/code>. (By comparison, icc also gets the order wrong, but the other way: It always loads <code>p<\/code> <i>last<\/i>.)<\/p>\n<p>\u00b9 Another practical use is to bypass any possible overloading of the <code>[]<\/code> operator, as noted in Chapter 14 of <a href=\"http:\/\/www.imperfectcplusplus.com\/\"> <i>Imperfect C++<\/i><\/a>:<\/p>\n<pre>#define ARRAYSIZE(a) (sizeof(a) \/ sizeof(0[a]))\r\n<\/pre>\n<p>By flipping the order in <code>0[a]<\/code>, this bypasses any possible <code>a[]<\/code> overloaded.<\/p>\n<pre>std::vector&lt;int&gt; v(5);\r\nint size = ARRAYSIZE(v); \/\/ compiler error\r\n<\/pre>\n<p>However, it isn&#8217;t foolproof. You just need to create a more clever fool: If <code>v<\/code> is a pointer or an object convertible to a pointer, then that pointer will happily go inside the <code>0[...]<\/code>.<\/p>\n<pre>struct Funny\r\n{\r\n    operator int*() { return oops; }\r\n    int oops[5];\r\n    int extra;\r\n};\r\n\r\nFunny f;\r\nint size1 = ARRAYSIZE(f); \/\/ oops: 6\r\n\r\nint* p = f;\r\nint size2 = ARRAYSIZE(p); \/\/ oops: 1\r\n<\/pre>\n<p>Fortunately, you don&#8217;t need any macro tricks. You can let C++ <code>constexpr<\/code> functions do the work for you:<\/p>\n<pre>template&lt;typename T, std::size_t N&gt;\r\nconstexpr std::size_t array_size(T(&amp;)[N]) { return N; }\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Possibly more than just a curiosity.<\/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-108005","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Possibly more than just a curiosity.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108005","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=108005"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108005\/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=108005"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108005"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108005"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}