默认复制/移动构造函数时 GDB 中的奇怪行为

Weird behavior in GDB when defaulting copy/move constructors

本文关键字:GDB 复制 移动 构造函数 默认      更新时间:2023-10-16

我有以下代码,它在 GDB 中似乎表现得很奇怪,具体取决于复制/移动构造函数是否默认。

#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,在 main 中的最后一个 cout 上添加一个断点并运行"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

一般来说,由于 test::get 的结果是"纯右值",如果编译器未绑定到左值(如 Percent&&(,则允许编译器跳过其初始化。因此,要查看纯右值的内容,您应该将其存储在 Percent&& 变量中,该变量"具体化"了 prvalue 并延长了临时返回值的生命周期。

这两种情况之间的差异似乎存在于可执行文件中(与 GDB 无关(:从两种情况下可执行文件的反汇编可以看出"test::get"不同,而如果我们在 (-O3( 上进行优化编译,生成的程序集是相同的。

案例 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 

由于在这种情况下您从 GDB 调用 test::get 因此您不是简单地在内存中打印一个值,而是在执行上面的行。你看到有一个对 Percent 复制构造函数的调用,test::get 的返回似乎在 rax 寄存器中,而似乎在第一个代码段中隐式构造函数是内联的,返回值存储在浮点寄存器 xmm0 中。 我不知道为什么会有这种差异(也许组装专家可以添加一些见解(,但我怀疑这就是GDB感到困惑的原因。