Странное поведение в GDB при невыполнении конструкторов copy/move

У меня есть следующий код, который, по-видимому, ведет себя странно в GDB в зависимости от того, установлены ли конструкторы copy/move по умолчанию или нет.

#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;
}

Я запускаю GDB, добавляю точку останова на последнем cout в main и запускаю "p t.get()", и я ожидаю, что это будет 42, но в зависимости от значения макроса CUSTOM я получаю либо 42 (когда CUSTOM равно 1) или 0 (когда CUSTOM равно 0).

Что происходит? Это ошибка в gdb, компилятор?

OS: Fedora 26
Compiler: gcc 7.3.1
Flags: -fsanitize=address,leak -O0 -g3 -std=c++17
GDB 8.0.1-36

Ответ 1

В общем, поскольку результат теста :: get является "чистым значением rvalue", компилятору разрешено пропустить его инициализацию, если он не привязан к lvalue (например, Percentage &&). Таким образом, чтобы увидеть содержимое чистого значения r, вы должны сохранить его в переменной Percentage &&, которая "материализует" значение prvalue и продлевает срок службы временного возврата.

Разница между двумя случаями, по-видимому, существует в исполняемом файле (ничего не связано с GDB): из разборки исполняемого файла видно, что "test :: get" отличается, а если мы скомпилируем с оптимизацией на ([ CN00]), сгенерированная сборка такая же.

Случай 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)

Случай 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 

Поскольку вы вызываете test :: get из GDB, в этом случае вы не просто печатаете значение в памяти, вы выполняете строки выше. Вы видите там вызов конструктора копирования Percentage, и возврат test :: get, по-видимому, находится в регистре rax, тогда как кажется, что в первом фрагменте неявный конструктор встроен, а возвращаемое значение сохраняется в регистр с плавающей запятой xmm0. Я не знаю, почему это различие (может быть, кто-то специалист в сборке может добавить некоторое понимание), но я подозреваю, почему GDB запутался.