I have the following code which seems to behave weirdly in GDB depending if the copy/move constructors are defaulted or not.
#include <iostream> #define CUSTOM 0 class Percentage { public: using value_t = double; Percentage() = default; ~Percentage() = default; template <typename T> Percentage(T) = delete; Percentage(value_t value): m_value(value) {} #if CUSTOM == 1 Percentage(const Percentage& p): m_value(p.m_value) {} Percentage& operator=(const Percentage& p) { m_value = p.m_value; return *this; } Percentage(Percentage&& p): m_value(std::move(p.m_value)) {} Percentage& operator=(Percentage&& p) { m_value = std::move(p.m_value); return *this; } #else Percentage(const Percentage&) = default; Percentage& operator=(const Percentage&) = default; Percentage(Percentage&&) = default; Percentage& operator=(Percentage&&) = default; #endif friend std::ostream& operator<<(std::ostream& os, const Percentage& p) { return os << (p.m_value * 100.0) << '%'; } private: value_t m_value = 0.0; }; struct test { Percentage m_p; void set(const Percentage& v) { m_p = v; } Percentage get() const { return m_p; } }; int main() { test t; std::cout << "Value 1: " << t.get() << std::endl; t.set(42.0); std::cout << "Value 2: " << t.get() << std::endl; std::cout << "Breakpoint here" << std::endl; }
I fire up GDB, add a breakpoint on the last cout in main and run “p t.get()” and I expect it to be 42 but depending on the value of the macro CUSTOM I get either 42 (when CUSTOM is 1) or 0 (when CUSTOM is 0).
What is happening ? Is this a bug in gdb, the compiler ?
OS: Fedora 26 Compiler: gcc 7.3.1 Flags: -fsanitize=address,leak -O0 -g3 -std=c++17 GDB 8.0.1-36
Advertisement
Answer
In general since the result of test::get is a “pure rvalue”, the compiler is allowed to skip its initialization if its not bound to an lvalue (like a Percentage&&). So to see the content of the a pure rvalue you should store it in a Percentage&& variable, which “materializes” the prvalue and extends the lifetime of the temporary returned.
The difference between the 2 cases seems to exist in the executable (nothing related to GDB): one can see from the disassembly of the executable in the two cases that “test::get” differs, while if we compile with optimization on (-O3) the assembly generated is the same.
Case 0:
Percentage get() { return m_p; } 4009f0: 55 push %rbp 4009f1: 48 89 e5 mov %rsp,%rbp 4009f4: 48 89 7d f0 mov %rdi,-0x10(%rbp) 4009f8: 48 8b 7d f0 mov -0x10(%rbp),%rdi 4009fc: 48 8b 3f mov (%rdi),%rdi 4009ff: 48 89 7d f8 mov %rdi,-0x8(%rbp) 400a03: f2 0f 10 45 f8 movsd -0x8(%rbp),%xmm0 400a08: 5d pop %rbp 400a09: c3 retq 400a0a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
Case 1:
Percentage get() { return m_p; } 4009f0: 55 push %rbp 4009f1: 48 89 e5 mov %rsp,%rbp 4009f4: 48 83 ec 10 sub $0x10,%rsp 4009f8: 48 89 f8 mov %rdi,%rax 4009fb: 48 89 75 f8 mov %rsi,-0x8(%rbp) 4009ff: 48 8b 75 f8 mov -0x8(%rbp),%rsi 400a03: 48 89 45 f0 mov %rax,-0x10(%rbp) 400a07: e8 54 00 00 00 callq 400a60 <_ZN10PercentageC2ERKS_> 400a0c: 48 8b 45 f0 mov -0x10(%rbp),%rax 400a10: 48 83 c4 10 add $0x10,%rsp 400a14: 5d pop %rbp 400a15: c3 retq 400a16: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 400a1d: 00 00 00
Since you call test::get from GDB in this case you are not simply printing a value in memory, you are executing the lines above. You see there’s a call to the copy constructor of Percentage, and the return of test::get seems to be in the rax register, while it seems that in the first snippet the implicit constructor is inlined, and the return value is stored in the floating point register xmm0. I don’t know why this difference (maybe someone expert in assembly can add some insight), but I suspect that’s why GDB gets confused.