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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With