demystifying value categories in c
play

Demystifying Value Categories in C++ iCSC 2020 Nis Meinert Rostock - PowerPoint PPT Presentation

Demystifying Value Categories in C++ iCSC 2020 Nis Meinert Rostock University Disclaimer Disclaimer This talk is mainly about hounding (unnecessary) copy ctors In case you dont care: If youre not at all interested in


  1. Q: What is the output of the program? 9 Demystifying Value Categories in C++ – Understanding References Nis Meinert – Rostock University 20 19 18 17 16 15 14 13 1 11 10 12 11 / 100 2 7 3 6 4 5 8 #include <iostream> struct S { int x; S( int x): x(x) { std::cout << 'a'; } S( const S& other): x(other.x) { std::cout << 'b'; } S& operator =( const S& other) { x = other.x; std::cout << 'c'; return * this ; } }; void swap(S*& a, S*& b) { S* tmp = a; a = b; b = tmp; } int main() { S a{1}; S b{2}; swap(&a, &b); std::cout << a.x << b.x; } godbolt.org/z/Eh656x

  2. Q: What is the output of the program? 9 Demystifying Value Categories in C++ – Understanding References Nis Meinert – Rostock University 20 19 18 17 16 15 14 13 12 11 10 11 / 100 4 1 7 6 2 3 5 8 error: cannot bind non-const lvalue reference of type “ S*& ” to an rvalue of type “ S* ” #include <iostream> struct S { int x; S( int x): x(x) { std::cout << 'a'; } S( const S& other): x(other.x) { std::cout << 'b'; } S& operator =( const S& other) { x = other.x; std::cout << 'c'; return * this ; } }; void swap(S*& a, S*& b) { S* tmp = a; a = b; b = tmp; } int main() { S a{1}; S b{2}; swap(&a, &b); std::cout << a.x << b.x; } godbolt.org/z/Eh656x

  3. Value Categories

  4. Value categories with Venn diagrams Nis Meinert – Rostock University Demystifying Value Categories in C++ – Value Categories 12 / 100 (diagrams shamelessly stolen from bajamircea.github.io/coding/cpp/2016/04/07/move-forward.html )

  5. Value categories with Venn diagrams 8 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University 18 17 16 15 14 13 12 11 9 10 7 5 1 2 3 4 13 / 100 6 (diagrams shamelessly stolen from bajamircea.github.io/coding/cpp/2016/04/07/move-forward.html ) struct S{ int x; }; S make_S( int x) { S s{.x = x}; return s; // has no name after returning } int main() { S a = make_S(42); // `a` is an lvalue // initialized with a prvalue S b = std::move(a); // prepare to die, `a`! // now `a` became an xvalue auto x = a.x; // ERROR: `a` is in an undefined state a = make_S(13); x = a.x; // fine! }

  6. Value categories with Venn diagrams 8 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University 18 17 16 15 14 13 12 11 9 10 7 5 1 2 3 4 13 / 100 6 (diagrams shamelessly stolen from bajamircea.github.io/coding/cpp/2016/04/07/move-forward.html ) struct S{ int x; }; S make_S( int x) { S s{.x = x}; return s; // has no name after returning } int main() { S a = make_S(42); // `a` is an lvalue // initialized with a prvalue S b = std::move(a); // prepare to die, `a`! // now `a` became an xvalue auto x = a.x; // ERROR: `a` is in an undefined state a = make_S(13); x = a.x; // fine! }

  7. Value categories with Venn diagrams 8 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University 18 17 16 15 14 13 12 11 9 10 7 5 1 2 3 4 13 / 100 6 (diagrams shamelessly stolen from bajamircea.github.io/coding/cpp/2016/04/07/move-forward.html ) struct S{ int x; }; S make_S( int x) { S s{.x = x}; return s; // has no name after returning } int main() { S a = make_S(42); // `a` is an lvalue // initialized with a prvalue S b = std::move(a); // prepare to die, `a`! // now `a` became an xvalue auto x = a.x; // ERROR: `a` is in an undefined state a = make_S(13); x = a.x; // fine! }

  8. Binding references to temporaries 7 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University extension) → …except it is a const reference (lifetime has a name… → One cannot refer to something that doesn’t → Memory addresses are always rvalues! 8 14 / 100 6 5 4 3 2 1 error: cannot bind non-const lvalue reference of type “ S*& ” to an rvalue of type “ S* ” template < typename T> void swap(T& a, T& b) { ... } int main() { S a{1}; S b{2}; swap(&a, &b); }

  9. std::move

  10. 15 / 100 8 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University → Syntax: 13 12 11 1 9 10 7 4 2 6 3 5 std::move #include <iostream> #include <utility> struct S{}; → std::move creates xvalues void f( const S&) { std::cout << 'a'; } void f(S&&) { std::cout << 'b'; } int main() { → lvalue ref.: S& S s; → rvalue ref.: S&& f(s); // prints 'a' f(std::move(s)); // prints 'b' } godbolt.org/z/aKbGEc

  11. Q: What is the output of the program? 8 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University 15 14 13 12 11 10 1 9 7 5 2 3 4 16 / 100 6 #include <iostream> #include <utility> struct S{ S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } S(S&&) { std::cout << 'c'; } }; int main() { S s1; S s2(s1); S s3(S{}); S s4(std::move(s1)); } godbolt.org/z/16hYbz

  12. 17 / 100 elision (initializer is prvalue of the 11 13 10 9 14 8 15 7 6 12 same class type) 5 forced move construction 4 3 Nis Meinert – Rostock University 2 Demystifying Value Categories in C++ – Value Categories 1 A: abac #include <iostream> #include <utility> struct S{ → S s1 : no surprise S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } → S s2(s1) : no surprise S(S&&) { std::cout << 'c'; } }; → S s3(S{}) : mandatory copy int main() { S s1; S s2(s1); → S s4(std::move(s1)) : S s3(S{}); S s4(std::move(s1)); } godbolt.org/z/16hYbz

  13. Q: What is the output of the program? 9 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University 19 18 17 16 15 14 13 12 1 11 10 18 / 100 4 7 2 6 5 3 8 #include <iostream> #include <utility> struct S { S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } S(S&&) { std::cout << 'c'; } }; void f( const S&) { std::cout << '1'; } void f(S&) { std::cout << '2'; } void f(S&&) { std::cout << '3'; } int main() { S s1; f(s1); f(S{}); f(std::move(s1)); } godbolt.org/z/4MKojT

  14. 18 / 100 7 1 11 14 10 9 15 8 16 17 13 6 18 5 19 4 3 Nis Meinert – Rostock University 2 Demystifying Value Categories in C++ – Value Categories 12 A: a2a33 #include <iostream> #include <utility> struct S { S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } S(S&&) { std::cout << 'c'; } }; void f( const S&) { std::cout << '1'; } void f(S&) { std::cout << '2'; } void f(S&&) { std::cout << '3'; } int main() { S s1; f(s1); f(S{}); f(std::move(s1)); } godbolt.org/z/4MKojT

  15. Q: What is the output of the program? 9 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University 19 18 17 16 15 14 13 12 1 11 10 19 / 100 4 7 2 6 5 3 8 #include <iostream> #include <utility> struct S { S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } S(S&&) { std::cout << 'c'; } }; void f( const S&) { std::cout << '1'; } void f(S) { std::cout << '2'; } void f(S&&) { std::cout << '3'; } int main() { S s1; f(s1); f(S{}); f(std::move(s1)); } godbolt.org/z/jaYTYP

  16. Q: What is the output of the program? 10 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University lvalue, nor rvalue) copy and reference overloads! (neither Compile-time error (in all three cases) 19 18 17 16 15 1 13 12 11 14 9 7 2 3 4 6 5 20 / 100 8 #include <iostream> #include <utility> struct S { S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } S(S&&) { std::cout << 'c'; } → f(s1) : ambiguity between 2 and 1 }; → f(S{}) : ambiguity between 2 and 3 void f( const S&) { std::cout << '1'; } void f(S) { std::cout << '2'; } → f(std::move(s1) : same as f(S) void f(S&&) { std::cout << '3'; } → compiler cannot difgerentiate between ֒ int main() { S s1; f(s1); f(S{}); f(std::move(s1)); } godbolt.org/z/jaYTYP

  17. Q: What is the output of the program? 9 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University 18 17 16 15 14 13 12 11 1 10 21 / 100 4 8 7 6 2 5 3 #include <iostream> #include <utility> struct S { ~S() { std::cout << 'a'; } }; void f( const S&) { std::cout << '1'; } void f(S&) { std::cout << '2'; } void f(S&&) { std::cout << '3'; } int main() { S&& r1 = S{}; f(r1); S&& r2 = S{}; f(std::move(r2)); } godbolt.org/z/5s1zc5

  18. 22 / 100 9 15 18 14 1 13 anymore and which will die soon (cf. 12 11 lifetime extension!) 10 but makes the object look like a dying object 17 8 7 An rvalue has no name 6 NB: an rvalue ref behaves like an lvalue ref except that it can bind to a temporary (an rvalue), whereas one 5 cannot bind a (non const) lvalue ref to an rvalue. 4 3 Nis Meinert – Rostock University 2 Demystifying Value Categories in C++ – Value Categories 16 A: 23aa #include <iostream> → S&& : object that nobody cares about #include <utility> struct S { ~S() { std::cout << 'a'; } }; → std::move does not actually kill, void f( const S&) { std::cout << '1'; } void f(S&) { std::cout << '2'; } void f(S&&) { std::cout << '3'; } int main() { S&& r1 = S{}; f(r1); S&& r2 = S{}; f(std::move(r2)); } godbolt.org/z/5s1zc5

  19. 23 / 100 1 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University argument to an rvalue → unconditionally casts its runtime → does nothing at all during → does not destroy → does not move 7 5 6 4 3 2 std::move So what does std::move ? #include <type_traits> template < typename T> decltype ( auto ) move(T&& t) { using R = std::remove_reference_t<T>&&; return static_cast <R>(t); } godbolt.org/z/W8zb8G

  20. 24 / 100 6 1 3 8 4 7 5 6 5 2 7 4 8 3 Nis Meinert – Rostock University 2 Demystifying Value Categories in C++ – Value Categories 1 Quick Bench: tinyurl.com/y67sg7to std::vector< int > x(1000, 42); std::vector< int > y(1000, 42); for ( auto _ : state) { auto tmp = x; x = y; y = tmp; benchmark::DoNotOptimize(x[345] + y[678]); } std::vector< int > x(1000, 42); std::vector< int > y(1000, 42); for ( auto _ : state) { auto tmp = std::move(x); x = std::move(y); y = std::move(tmp); benchmark::DoNotOptimize(x[345] + y[678]); }

  21. Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University 25 / 100 Quick Bench: tinyurl.com/y67sg7to

  22. Universal References

  23. Rvalue ref. or no rvalue ref.? Rvalue refs are declared using “&&”: reasonable to assume that the presence of “&&” in Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 26 / 100 a type declaration indicates an rvalue reference? struct S{}; S&& s = S{}; // (1) Does “ && ” mean rvalue reference? → (1) : ??? auto && s2 = s; // (2) → (2) : ??? void f(S&& s); // (3) → (3) : ??? template < typename T> void f(T&& t); // (4) → (4) : ??? → (5) : ??? template < typename T> void f( const T&& t); // (5) → (6) : ??? template < typename T> void f(std::vector<T>&& v); // (6)

  24. Rvalue ref. or no rvalue ref.? Rvalue refs are declared using “&&”: reasonable to assume that the presence of “&&” in Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 26 / 100 a type declaration indicates an rvalue reference? No! struct S{}; S&& s = S{}; // (1) Does “ && ” mean rvalue reference? → (1) : yes auto && s2 = s; // (2) → (2) : no void f(S&& s); // (3) → (3) : yes template < typename T> void f(T&& t); // (4) → (4) : no → (5) : yes ⋆ template < typename T> void f( const T&& t); // (5) → (6) : yes template < typename T> void f(std::vector<T>&& v); // (6) ⋆ albeit questionable: move changes object in most cases �↔ const

  25. 27 / 100 6 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 12 11 10 9 7 8 3 4 1 2 5 std::move and const ⋆ albeit questionable: move changes object in most cases �↔ const #include <iostream> struct S { S() {} S( const S&) { std::cout << 'A'; } S(S&&) { std::cout << 'B'; } }; int main() { const S s; auto s2 = std::move(s); } godbolt.org/z/r9hv8K …prints A (cf. https://stackoverflow.com/a/28595415 )

  26. Universal references reduce: Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University references! Universal reference are always 28 / 100 → Rule of thumb: substitute fully Universal references † std::vector<S> v; → Syntax ( x is a universal reference): auto && s = v[0]; // S&&& -> S& → auto&& x auto && s2 = S{}; // S&&&& -> S&& → template <typename T> auto && s3 = s2; // S&&& -> S& f(T&& x… // S&&&& -> S&& auto && s3 = std::move(s2); qualified type into auto or T and /* Exception */ S s4{}; auto && s5 = s4; // S&& -> S& → && �→ && → &&& �→ & → &&&& �→ && † Universal reference : term introduced by Scott Meyers

  27. Q: What is the output of the program? 9 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 17 16 15 14 13 12 11 1 10 29 / 100 8 3 7 6 5 2 4 #include <iostream> #include <type_traits> struct S { S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } S(S&&) { std::cout << 'c'; } }; template < typename T> S f(T&& t) { return t; } int main() { S s{}; f(s); f(std::move(s)); } godbolt.org/z/6xn1n3

  28. 29 / 100 16 11 1 10 9 14 8 15 7 6 12 17 5 …how to preserve the value category? 4 3 Nis Meinert – Rostock University 2 Demystifying Value Categories in C++ – Universal References 13 A: abb #include <iostream> #include <type_traits> struct S { S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } S(S&&) { std::cout << 'c'; } }; template < typename T> S f(T&& t) { return t; } int main() { S s{}; f(s); f(std::move(s)); } godbolt.org/z/6xn1n3

  29. Q: What is the output of the program? 10 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 19 18 17 16 15 14 13 12 1 11 30 / 100 4 8 7 2 6 5 3 9 #include <iostream> #include <type_traits> struct S{}; void f(S&) { std::cout << 'a'; } void f(S&&) { std::cout << 'b'; } int main() { auto && r1 = S{}; static_assert(std::is_same_v< decltype (r1), S&&>); f(r1); f( static_cast <S&&>(r1)); S s; auto && r2 = s; static_assert(std::is_same_v< decltype (r2), S&>); f(r2); } godbolt.org/z/zTExze

  30. 30 / 100 17 12 1 11 15 10 16 9 8 7 13 18 6 5 19 4 3 Nis Meinert – Rostock University 2 Demystifying Value Categories in C++ – Universal References 14 A: aba #include <iostream> #include <type_traits> struct S{}; void f(S&) { std::cout << 'a'; } void f(S&&) { std::cout << 'b'; } int main() { auto && r1 = S{}; static_assert(std::is_same_v< decltype (r1), S&&>); f(r1); f( static_cast <S&&>(r1)); S s; auto && r2 = s; static_assert(std::is_same_v< decltype (r2), S&>); f(r2); } godbolt.org/z/zTExze

  31. Q: What is the output of the program? 8 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 16 15 14 13 12 11 1 9 10 7 5 2 3 4 31 / 100 6 #include <iostream> struct S{ void f() & { std::cout << 'a'; } void f() && { std::cout << 'b'; } }; int main() { auto && r1 = S{}; r1.f(); static_cast < decltype (r1)>(r1).f(); auto && r2 = r1; r2.f(); static_cast < decltype (r2)>(r2).f(); } godbolt.org/z/WcYYsd

  32. 31 / 100 8 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 16 15 14 13 12 11 1 9 10 7 5 2 3 6 4 A: abaa #include <iostream> struct S{ void f() & { std::cout << 'a'; } void f() && { std::cout << 'b'; } }; int main() { auto && r1 = S{}; r1.f(); static_cast < decltype (r1)>(r1).f(); auto && r2 = r1; r2.f(); static_cast < decltype (r2)>(r2).f(); } godbolt.org/z/WcYYsd

  33. Perfect forwarding 7 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 6 5 4 3 2 1 How do we fuse these implementations? 8 9 32 / 100 3 6 1 5 2 4 // if `t` is an lvalue of type `T` template < typename T> T& forward(T& t) { return t; } // if `t` is an rvalue of type `T` template < typename T> T&& forward(T& t) { return std::move(t); // static_cast<T&&>(t) } #include <type_traits> template < typename T> T&& forward(std::remove_reference_t<T>& t) { return static_cast <T&&>(t); } godbolt.org/z/EjPnPr

  34. Q: What is the output of the program? 9 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 17 16 15 14 13 12 11 1 10 33 / 100 8 3 7 6 5 2 4 #include <iostream> #include <type_traits> struct S { S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } S(S&&) { std::cout << 'c'; } }; template < typename T> S f(T&& t) { return std::forward<T>(t); } int main() { S s{}; f(s); f(std::move(s)); } godbolt.org/z/7Worb3

  35. 34 / 100 9 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University references 17 16 15 14 13 12 1 10 11 8 6 2 7 3 4 5 A: abc #include <iostream> #include <type_traits> struct S { S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } S(S&&) { std::cout << 'c'; } }; template < typename T> S f(T&& t) { return std::forward<T>(t); } int main() { S s{}; f(s); f(std::move(s)); } godbolt.org/z/7Worb3 Rule of thumb: Use std::move for rvalues and std::forward for universal

  36. Q: Why can't we use perfect forwarding here? 6 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 9 8 7 1 35 / 100 5 4 3 2 #include <functional> template < typename Iter, typename Callable, typename ... Args> void foreach (Iter current, Iter end, Callable op, const Args&... args) { while (current != end) { std::invoke(op, args..., *current); ++current; } } godbolt.org/z/TvnEfT

  37. Q: Why can't we use perfect forwarding here? 6 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University A: The first call in the loop might steal the values, leading to unexpected behavior 9 8 1 7 5 4 3 2 35 / 100 #include <functional> template < typename Iter, typename Callable, typename ... Args> void foreach (Iter current, Iter end, Callable op, const Args&... args) { while (current != end) { std::invoke(op, args..., *current); ++current; } } godbolt.org/z/TvnEfT calling op in subsequent iterations.

  38. Reading x86-64 Assembly …for fun and profit

  39. Function Prologue & Epilogue alternatively Demystifying Value Categories in C++ – Reading Assembly for Fun and Profit Nis Meinert – Rostock University 2 1 alternatively 3 2 1 Epilogue → Few lines of code at the beginning ( prologue ) and end ( epilogue ) of a function, 1 36 / 100 Prologue → registers 2 which prepares (and eventually restores) 1 3 → the stack and and compilers) → Not part of assembly: convention (defined & interpreted difgerently by difgerent OS push rbp ; rbp: frame pointer mov rsp, rbp mov rbp, rsp ; rsp: stack pointer pop rbp sub rsp, N ret enter N, 0 leave ret (reserve N bytes on stack for local use)

  40. Stack frame for function call (stack frame for function call with 8 arguments and Demystifying Value Categories in C++ – Reading Assembly for Fun and Profit Nis Meinert – Rostock University 37 / 100 → pass arguments 1 …6 in transfers control there ┌──────────────┐ │ ... │ │ 8th Argument │ (rbp + 24) │ 7th Argument │ (rbp + 16) → CALL = PUSH address of next ├──────────────┤ instruction + JMP target │ rip │ (return address) │ rbp │ (rbp) → RET pops return address and ├──────────────┤ │ rbx │ │ r12 │ registers ( rsi , rdx , …) │ r13 │ (rsp) └──────────────┘ local registers rbx , r12 and r13 )

  41. 38 / 100 Nis Meinert – Rostock University Demystifying Value Categories in C++ – Reading Assembly for Fun and Profit lea vs. mov ┌──────────────────┐ │ Registers │ → lea : load efgective address ├──────────────────┤ → puts memory address from src into │ EAX = 0x00000000 │ the destination dest │ EBX = 0x00403A40 │ → Example: lea eax, [ebx+8] └──────────────────┘ ┌────────────┐ → put [ebx+8] into eax │ Memory │ → value of eax afuer instruction: ├────────────┤ 0x00403A48 0x00403A40 │ 0x7C81776F │ → …whereas: mov eax, [ebx+8] 0x00403A44 │ 0x7C911000 │ → value of eax afuer instruction: 0x00403A48 │ 0x0012C140 │ 0x0012C140 0x00403A4C │ 0x7FFDB000 │ └────────────┘

  42. Reading assembly for fun and profit 4 Demystifying Value Categories in C++ – Reading Assembly for Fun and Profit Nis Meinert – Rostock University 1 39 / 100 2 3 int f( int x, int y, int z) { # g92 -O0 int sum = x + y + z; | f(int, int, int): return sum; 1| push rbp } 1| mov rbp, rsp 1| mov DWORD PTR [rbp-20], edi godbolt.org/z/MaWcP9 1| mov DWORD PTR [rbp-24], esi 1| mov DWORD PTR [rbp-28], edx 2| mov edx, DWORD PTR [rbp-20] 2| mov eax, DWORD PTR [rbp-24] 2| add edx, eax 2| mov eax, DWORD PTR [rbp-28] 2| add eax, edx 2| mov DWORD PTR [rbp-4], eax 3| mov eax, DWORD PTR [rbp-4] 4| pop rbp 4| ret godbolt.org/z/MaWcP9

  43. Reading assembly for fun and profit 4 Demystifying Value Categories in C++ – Reading Assembly for Fun and Profit Nis Meinert – Rostock University 1 39 / 100 2 3 int f( int x, int y, int z) { # g92 -O0 int sum = x + y + z; | f(int, int, int): return sum; 1| push rbp } 1| mov rbp, rsp 1| mov DWORD PTR [rbp-20], edi godbolt.org/z/MaWcP9 1| mov DWORD PTR [rbp-24], esi 1| mov DWORD PTR [rbp-28], edx 2| mov edx, DWORD PTR [rbp-20] # g92 -O1 2| mov eax, DWORD PTR [rbp-24] | f(int, int, int): 2| add edx, eax 2| add edi, esi 2| mov eax, DWORD PTR [rbp-28] 2| lea eax, [rdi+rdx] 2| add eax, edx 4| ret 2| mov DWORD PTR [rbp-4], eax 3| mov eax, DWORD PTR [rbp-4] godbolt.org/z/67WsqT 4| pop rbp 4| ret godbolt.org/z/MaWcP9

  44. Reading assembly for fun and profit 5 Demystifying Value Categories in C++ – Reading Assembly for Fun and Profit Nis Meinert – Rostock University 1 7 6 40 / 100 4 3 2 int f( int x) { # g92 -O0 return x + 1; | f(int): } 1| push rbp 1| mov rbp, rsp int g( int x) { 1| mov DWORD PTR [rbp-4], edi return f(x + 2); 2| mov eax, DWORD PTR [rbp-4] } 2| add eax, 1 3| pop rbp godbolt.org/z/87GK4q 3| ret | g(int): 5| push rbp 5| mov rbp, rsp 5| sub rsp, 8 5| mov DWORD PTR [rbp-4], edi 6| mov eax, DWORD PTR [rbp-4] 6| add eax, 2 6| mov edi, eax 6| call f(int) 7| leave 7| ret godbolt.org/z/87GK4q

  45. Reading assembly for fun and profit 5 Demystifying Value Categories in C++ – Reading Assembly for Fun and Profit Nis Meinert – Rostock University 1 7 6 40 / 100 4 3 2 int f( int x) { # g92 -O0 return x + 1; | f(int): } 1| push rbp 1| mov rbp, rsp int g( int x) { 1| mov DWORD PTR [rbp-4], edi return f(x + 2); 2| mov eax, DWORD PTR [rbp-4] } 2| add eax, 1 3| pop rbp godbolt.org/z/87GK4q 3| ret | g(int): 5| push rbp # g92 -O1 5| mov rbp, rsp | f(int): 5| sub rsp, 8 2| lea eax, [rdi+1] 5| mov DWORD PTR [rbp-4], edi 3| ret 6| mov eax, DWORD PTR [rbp-4] | g(int): 6| add eax, 2 2| lea eax, [rdi+3] 6| mov edi, eax 7| ret 6| call f(int) 7| leave godbolt.org/z/Yxbb6q 7| ret godbolt.org/z/87GK4q

  46. Reading assembly for fun and profit 5 Demystifying Value Categories in C++ – Reading Assembly for Fun and Profit Nis Meinert – Rostock University 1 7 6 41 / 100 3 4 2 void side_effect(); # g92 -O0 | f(int): int f( int x) { 3| push rbp auto a = x; 3| mov rbp, rsp side_effect(); 3| sub rsp, 32 return a - x; 3| mov DWORD PTR [rbp-20], edi } 4| mov eax, DWORD PTR [rbp-20] 4| mov DWORD PTR [rbp-4], eax godbolt.org/z/5xq5n5 5| call side_effect() 6| mov eax, DWORD PTR [rbp-4] 6| sub eax, DWORD PTR [rbp-20] 7| leave 7| ret godbolt.org/z/5xq5n5

  47. 42 / 100 1 Nis Meinert – Rostock University 2 3 7 4 6 5 5 6 4 7 3 2 1 Implicit costs of using const& void side_effect(); void side_effect(); int f( int x) { int f( const int & x) { auto a = x; auto a = x; side_effect(); side_effect(); return a - x; return a - x; } } godbolt.org/z/5xq5n5 godbolt.org/z/333ME7 Demystifying Value Categories in C++ – Implicit costs of const&

  48. 43 / 100 Nis Meinert – Rostock University Implicit costs of using const& # g92 -O0 # g92 -O0 | f(int): | f(int const&): 3| push rbp 3| push rbp 3| mov rbp, rsp 3| mov rbp, rsp 3| sub rsp, 32 3| sub rsp, 32 3| mov DWORD PTR [rbp-20], edi 3| mov QWORD PTR [rbp-24], rdi 4| mov eax, DWORD PTR [rbp-20] 4| mov rax, QWORD PTR [rbp-24] 4| mov DWORD PTR [rbp-4], eax 4| mov eax, DWORD PTR [rax] 5| call side_effect() 4| mov DWORD PTR [rbp-4], eax 6| mov eax, DWORD PTR [rbp-4] 5| call side_effect() 6| sub eax, DWORD PTR [rbp-20] 6| mov rax, QWORD PTR [rbp-24] 7| leave 6| mov eax, DWORD PTR [rax] 7| ret 6| mov edx, DWORD PTR [rbp-4] 6| sub edx, eax godbolt.org/z/5xq5n5 6| mov eax, edx 7| leave 7| ret godbolt.org/z/333ME7 Demystifying Value Categories in C++ – Implicit costs of const&

  49. 44 / 100 necessary when function is not a leaf function Nis Meinert – Rostock University ommitted in leaf functions.) since callee have to know where to start saving Implicit costs of using const& # g92 -O3 # g92 -O3 | f(int): | f(int const&): 3| sub rsp, 8 3| push rbp 5| call side_effect() 3| push rbx 7| xor eax, eax 3| mov rbx, rdi 7| add rsp, 8 3| sub rsp, 8 7| ret 4| mov ebp, DWORD PTR [rdi] 5| call side_effect() godbolt.org/z/od8v6e 6| mov eax, ebp 6| sub eax, DWORD PTR [rbx] NB #1: adjusting rsp in function prologue 7| add rsp, 8 7| pop rbx 7| pop rbp 7| ret variables on stack. (Adjusting rsp can be godbolt.org/z/cr8f9b Demystifying Value Categories in C++ – Implicit costs of const&

  50. 44 / 100 optimizations such as alignment: ABI requires Nis Meinert – Rostock University stack to be aligned to 16 bytes. Implicit costs of using const& # g92 -O3 # g92 -O3 | f(int): | f(int const&): 3| sub rsp, 8 3| push rbp 5| call side_effect() 3| push rbx 7| xor eax, eax 3| mov rbx, rdi 7| add rsp, 8 3| sub rsp, 8 7| ret 4| mov ebp, DWORD PTR [rdi] 5| call side_effect() godbolt.org/z/od8v6e 6| mov eax, ebp 6| sub eax, DWORD PTR [rbx] NB #2: Ofgset x in sub rsp, x is objective of 7| add rsp, 8 7| pop rbx 7| pop rbp 7| ret godbolt.org/z/cr8f9b Demystifying Value Categories in C++ – Implicit costs of const&

  51. 45 / 100 8 Nis Meinert – Rostock University 2 Even though we only pass a reference, we pay the cost of the complex object 3 4 1 5 10 6 9 7 Implicit costs of using const& #include <string> # clang900 -O3 -std=c++2a -stdlib=libc++ #include <string_view> | get_size(std::string const&): xx| movzx eax, byte ptr [rdi] auto get_size( const std::string& s) { xx| test al, 1 return s.size(); xx| je .LBB0_1 } 0| mov rax, qword ptr [rdi + 8] 5| ret auto get_size(std::string_view sv) { | .LBB0_1: return sv.size(); 0| shr rax } 5| ret | get_size(std::string_view): godbolt.org/z/Yc1hrj 8| mov rax, rsi 9| ret | godbolt.org/z/Yc1hrj std::string (i.e., first bit is tested for short string optimization) → prefer views such as std::string_view or std::span ֒ Demystifying Value Categories in C++ – Implicit costs of const&

  52. 45 / 100 7 Nis Meinert – Rostock University 2 1 3 4 10 5 9 6 8 Implicit costs of using const& #include <string> # clang900 -O3 -std=c++2a -stdlib=libc++ #include <string_view> | get_size(std::string const&): xx| movzx eax, byte ptr [rdi] auto get_size( const std::string& s) { xx| test al, 1 return s.size(); xx| je .LBB0_1 } 0| mov rax, qword ptr [rdi + 8] 5| ret auto get_size(std::string_view sv) { | .LBB0_1: return sv.size(); 0| shr rax } 5| ret | get_size(std::string_view): godbolt.org/z/Yc1hrj 8| mov rax, rsi 9| ret | godbolt.org/z/Yc1hrj ⋆ Confession: switching to libstdc++ resolves this issue here Demystifying Value Categories in C++ – Implicit costs of const&

  53. Will it compile? 6 Nis Meinert – Rostock University 2 1 9 8 1 7 46 / 100 5 4 3 2 #include <array> #include <span> int main() { constexpr std::array x{ 4, 8, 15, 16, 23, 42 }; constexpr std::span x_view{x}; } godbolt.org/z/66exs6 template < typename T, std::size_t N> constexpr span( const std::array<T, N>& arr) noexcept ; cppreference.com/w/cpp/container/span/span Demystifying Value Categories in C++ – Implicit costs of const&

  54. Will it compile? 8 Nis Meinert – Rostock University 2 1 → Solutions? expressions! objects are not constant → References to automatic storage No! 1 9 7 5 2 3 4 46 / 100 6 #include <array> #include <span> int main() { → Constructor takes by reference constexpr std::array x{ 4, 8, 15, 16, 23, 42 }; constexpr std::span x_view{x}; } godbolt.org/z/66exs6 template < typename T, std::size_t N> constexpr span( const std::array<T, N>& arr) noexcept ; cppreference.com/w/cpp/container/span/span Demystifying Value Categories in C++ – Implicit costs of const&

  55. Will it compile? 6 Nis Meinert – Rostock University 9 8 7 1 47 / 100 5 4 3 2 #include <array> #include <span> int main() { constexpr static std::array x{ 4, 8, 15, 16, 23, 42 }; constexpr std::span x_view{x}; } godbolt.org/z/Ga5Ysv Demystifying Value Categories in C++ – Implicit costs of const&

  56. Nota bene … 6 Nis Meinert – Rostock University 12 11 10 9 8 this will work though, since reference / pointer does not escape constant expression … 7 48 / 100 5 4 1 3 2 #include <array> #include <span> constexpr auto f() { std::array x{4, 8, 15, 16, 23, 42}; std::span x_view{x}; return 0; } int main() { static_assert(f() == 0); } godbolt.org/z/rso3na Demystifying Value Categories in C++ – Implicit costs of const&

  57. Time to grab some covfefe

  58. A Short Quiz for the Break 5 Nis Meinert – Rostock University 9 8 7 1 6 3 4 2 48 / 100 #!/usr/bin/env python3 def f(x): if x + 1 is 1 + x: return False if x + 2 is not 2 + x: return False return True Find all x for which f(x) returns True ! Demystifying Value Categories in C++ – Implicit costs of const&

  59. A Short Quiz for the Break 6 Nis Meinert – Rostock University 9 8 7 1 48 / 100 5 4 3 2 #!/usr/bin/env python3 def f(x): if x + 1 is 1 + x: return False if x + 2 is not 2 + x: return False return True Find all x for which f(x) returns True ! Answer: f(x=-7) Demystifying Value Categories in C++ – Implicit costs of const&

  60. PART II

  61. Dangling References

  62. Will it compile? 7 Demystifying Value Categories in C++ – Dangling References Nis Meinert – Rostock University 13 12 11 10 9 1 8 49 / 100 3 2 5 4 6 struct S { int x; }; auto f() { S s{.x = 42}; return s; } int main() { S& s = f(); return s.x; } godbolt.org/z/x4rWKj

  63. Will it compile? 8 Demystifying Value Categories in C++ – Dangling References Nis Meinert – Rostock University error: cannot bind non-const lvalue reference of type “S&” to an rvalue of type “S” 13 12 11 10 9 1 49 / 100 7 3 6 2 5 4 struct S { int x; }; auto f() { S s{.x = 42}; return s; } int main() { S& s = f(); return s.x; } godbolt.org/z/x4rWKj

  64. Will it invoke undefined behavior? 8 Demystifying Value Categories in C++ – Dangling References Nis Meinert – Rostock University …binding a reference to a temporary??? 13 12 11 10 9 1 50 / 100 7 3 6 2 5 4 struct S { int x; }; auto f() { S s{.x = 42}; return s; } int main() { const S& s = f(); return s.x; } godbolt.org/z/avGMPa

  65. Temporary object lifetime extension 8 Demystifying Value Categories in C++ – Dangling References Nis Meinert – Rostock University binding to a const lvalue reference or to an rvalue reference (since C++11).” 13 12 11 10 9 1 51 / 100 7 3 6 2 5 4 struct S { int x; }; auto f() { S s{.x = 42}; return s; } int main() { const S& s = f(); return s.x; } godbolt.org/z/avGMPa cppreference.com : “The lifetime of a temporary object may be extended by

  66. Q: What is the output of the program? 10 Demystifying Value Categories in C++ – Dangling References Nis Meinert – Rostock University 20 19 18 17 16 15 14 13 12 1 11 52 / 100 9 6 2 3 4 5 7 8 #include <iostream> template < char id> struct Log { Log() { std::cout << id << 1; } virtual ~Log() { std::cout << id << 2; } }; struct A: Log<'a'> { int x; A( int x): x(x) {}; }; struct B: Log<'b'> { const A& a; B( const A& a): a(a) {} }; int main() { const B& b = B{A{42}}; std::cout << 'x'; return b.a.x; } godbolt.org/z/hcods4

  67. 53 / 100 20 1 14 15 16 17 18 19 Dangling reference!!! 11 → lifetime extension only for result of the temporary expression, not any sub-expression → use address sanitizer! Nis Meinert – Rostock University Demystifying Value Categories in C++ – Dangling References 12 13 6 3 5 7 4 8 9 2 10 A: a1b1a2xb2 #include <iostream> template < char id> struct Log { Log() { std::cout << id << 1; } virtual ~Log() { std::cout << id << 2; } }; struct A: Log<'a'> { int x; A( int x): x(x) {}; }; struct B: Log<'b'> { const A& a; B( const A& a): a(a) {} }; int main() { const B& b = B{A{42}}; std::cout << 'x'; return b.a.x; } godbolt.org/z/hcods4

  68. contrived?

  69. Reference lifetime extension 1 2 3 4 Nis Meinert – Rostock University Demystifying Value Categories in C++ – Dangling References 54 / 100 (derived from abseil.io : Tip of the Week #107: “Reference Lifetime Extension”) std::vector<std::string_view> explode( const std::string& s); for (std::string_view s: explode(str_cat("oo", "ps"))) { // WRONG! [...]

  70. Q: What is the output of the program? 6 Demystifying Value Categories in C++ – Dangling References Nis Meinert – Rostock University 9 8 7 1 55 / 100 5 4 3 2 #include <vector> int main() { std::vector< int > v; v.push_back(1); auto & x = v[0]; v.push_back(2); return x; } godbolt.org/z/M6bx1Y

  71. Q: What is the output of the program? 7 Demystifying Value Categories in C++ – Dangling References Nis Meinert – Rostock University → use address sanitizer! is pushed the space the second time an element Dangling reference!!! 9 8 1 55 / 100 6 5 4 3 2 #include <vector> int main() { std::vector< int > v; → std::vector needs to reallocate all v.push_back(1); auto & x = v[0]; v.push_back(2); return x; } godbolt.org/z/M6bx1Y

  72. std::move in the wild

  73. 56 / 100 8 Nis Meinert – Rostock University 9 8 7 6 5 4 3 2 (derived from CppCon 2019: Ben Deane “Everyday Efgiciency: In-Place Construction (Back to Basics?)”) 9 1 7 5 1 2 6 3 4 Moving std::string static void cp_small_str(benchmark::State& state) { for ( auto _ : state) { std::string original("small"); benchmark::DoNotOptimize(original); std::string copied = original; benchmark::DoNotOptimize(copied); } } BENCHMARK(cp_small_str); static void mv_small_str(benchmark::State& state) { for ( auto _ : state) { std::string original("small"); benchmark::DoNotOptimize(original); std::string moved = std::move(original); benchmark::DoNotOptimize(moved); } } BENCHMARK(mv_small_str); Demystifying Value Categories in C++ – std::move in the wild

  74. 57 / 100 8 Nis Meinert – Rostock University 9 8 7 6 5 4 3 2 (derived from CppCon 2019: Ben Deane “Everyday Efgiciency: In-Place Construction (Back to Basics?)”) 9 1 7 5 1 2 6 3 4 Moving std::string static void cp_long_str(benchmark::State& state) { for ( auto _ : state) { std::string original("this is too long for short string optimization"); benchmark::DoNotOptimize(original); std::string copied = original; benchmark::DoNotOptimize(copied); } } BENCHMARK(cp_long_str); static void mv_long_str(benchmark::State& state) { for ( auto _ : state) { std::string original("this is too long for short string optimization"); benchmark::DoNotOptimize(original); std::string moved = std::move(original); benchmark::DoNotOptimize(moved); } } BENCHMARK(mv_long_str); Demystifying Value Categories in C++ – std::move in the wild

  75. Quick Bench result Nis Meinert – Rostock University 58 / 100 Moving std::string Quick Bench: tinyurl.com/yybmdngv Demystifying Value Categories in C++ – std::move in the wild

  76. 1. copy stack allocated data 1. copy stack allocated data 2. set string length of moved string to zero Nis Meinert – Rostock University 59 / 100 Moving std::string Copy small std::string Move small std::string → moving is not necessarily better than copying! ֒ Demystifying Value Categories in C++ – std::move in the wild

  77. 60 / 100 sentinel node, because moved from Nis Meinert – Rostock University need to allocate → Move assignment can swap, thus no (albeit in an unspecified state) container must still be a valid container → Move ctor needs to allocate new Moving std::map Did they forget to mark the move ctor noexcept ? // since C++11 std::map( const std::map&&) // until C++17 std::map& operator =(std::map&&) // since C++17 std::map& operator =(std::map&&) noexcept Demystifying Value Categories in C++ – std::move in the wild

  78. 60 / 100 → Move ctor needs to allocate new Nis Meinert – Rostock University need to allocate → Move assignment can swap, thus no (albeit in an unspecified state) sentinel node, because moved from container must still be a valid container Moving std::map Did they forget to mark the move ctor noexcept ? No! // since C++11 std::map( const std::map&&) // until C++17 std::map& operator =(std::map&&) // since C++17 std::map& operator =(std::map&&) noexcept → move ctor of std::map allocates heap space! ֒ (Billy O’Neal: twitter.com/MalwareMinigun/status/1165310509022736384 ) Demystifying Value Categories in C++ – std::move in the wild

  79. 61 / 100 6 2 10 3 9 4 8 5 7 6 1 7 5 8 4 9 3 10 2 Nis Meinert – Rostock University 1 Moving std::map static void no_move(benchmark::State& state) { for ( auto _ : state) { auto m = []() -> std::map< int , int > { std::map< int , int > m{{0, 42}}; return m; }(); benchmark::DoNotOptimize(m); } } BENCHMARK(no_move); static void force_move(benchmark::State& state) { for ( auto _ : state) { auto m = []() -> std::map< int , int > { std::map< int , int > m{{0, 42}}; return std::move(m); }(); benchmark::DoNotOptimize(m); } } BENCHMARK(force_move); Demystifying Value Categories in C++ – std::move in the wild

  80. 62 / 100 5 Nis Meinert – Rostock University 9 8 7 1 6 4 3 2 Moving std::map static void copy(benchmark::State& state) { for ( auto _ : state) { std::map< int , int > m{{0, 42}}; benchmark::DoNotOptimize(m); auto m2 = m; benchmark::DoNotOptimize(m2); } } BENCHMARK(copy); Demystifying Value Categories in C++ – std::move in the wild

  81. Quick Bench result Nis Meinert – Rostock University 63 / 100 Moving std::map Quick Bench: tinyurl.com/y57egvjp Demystifying Value Categories in C++ – std::move in the wild

Download Presentation
Download Policy: The content available on the website is offered to you 'AS IS' for your personal information and use only. It cannot be commercialized, licensed, or distributed on other websites without prior consent from the author. To download a presentation, simply click this link. If you encounter any difficulties during the download process, it's possible that the publisher has removed the file from their server.

Recommend


More recommend