Skip to content
Advertisement

Weird behavior in GDB when defaulting copy/move constructors

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.

User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement