【问题标题】:C++ destructor fail on custom buffer - How does compiler really generate destructors' code?C++ 析构函数在自定义缓冲区上失败 - 编译器如何真正生成析构函数的代码?
【发布时间】:2016-12-13 17:38:02
【问题描述】:

我正在使用 Visual Studio 2015 构建一些单线程 C++ 项目。

我有一个非常粗糙的池,项目的其余部分会获取内存并返回给它。它在return 0; 之前运行没有问题。在那之后,我知道有一些编译器生成的退出代码,其中它调用了我自己的类的一些标量删除析构函数(下面的class foo)。

这是标量删除析构函数的一些汇编代码(我用aaaaafoo 隐藏了这些类和命名空间的名称):

000000013FC30FC0  mov         dword ptr [rsp+10h],edx  
000000013FC30FC4  mov         qword ptr [rsp+8],rcx  
000000013FC30FC9  push        rbp  
000000013FC30FCA  push        rdi  
000000013FC30FCB  sub         rsp,0E8h  
000000013FC30FD2  lea         rbp,[rsp+20h]  
000000013FC30FD7  mov         rdi,rsp  
000000013FC30FDA  mov         ecx,3Ah  
000000013FC30FDF  mov         eax,0CCCCCCCCh  
000000013FC30FE4  rep stos    dword ptr [rdi]  
000000013FC30FE6  mov         rcx,qword ptr [rsp+108h]  
000000013FC30FEE  mov         rcx,qword ptr [this]  
000000013FC30FF5  call        aaaaa::foo::~foo (013FC2381Bh)  
000000013FC30FFA  mov         eax,dword ptr [rbp+0E8h]  
000000013FC31000  and         eax,1  
000000013FC31003  test        eax,eax  
000000013FC31005  je          aaaaa::foo::`scalar deleting destructor'+58h (013FC31018h)  
000000013FC31007  mov         edx,30h  
000000013FC3100C  mov         rcx,qword ptr [this]  
000000013FC31013  call        operator delete (013FC2661Ah)  
000000013FC31018  mov         rax,qword ptr [this]  
000000013FC3101F  lea         rsp,[rbp+0C8h]  
000000013FC31026  pop         rdi  
000000013FC31027  pop         rbp  
000000013FC31028  ret

在调试会话中,我终于找出了问题所在。在正常运行阶段(即main函数的主要部分),000000013FC30FFA mov eax,dword ptr [rbp+0E8h]会将eax设置为0,从而绕过je之后的那几行代码,最关键的是绕过对operator delete的调用.

但是在这些行的最后一击中,eaxmov 之后被设置为1,然后执行对operator delete 的调用,并且恰好在该行它在@987654333 处触发了一个断点@

所以我的问题是:

  1. 在 Visual Studio 中标量删除析构函数是一个独特的概念,也就是说,它在任何其他编译器中都没有看到吗? (这在 immibis 的回答中得到了完美的回答,是的)
  2. 在什么情况下,调用我的自定义析构函数后,上面的代码会将eax 设置为1?为什么编译器会这样,也就是原来要删除的对象operator delete是什么?
  3. operator delete的执行是双重删除吗?
  4. 我必须追溯到rbp+0E8h 上的更改以查看错误是如何发生的吗? (还有其他方法吗?)

编辑关于课程: foo 类(这是我上面提到的我自己的类)具有所有(原始)指针成员,它们本身拥有这些指针的所有所有权,即负责删除它们。下面的buffer_t 是底层池或缓冲区,它基本上是一大块原始内存分区,为单个(即不是数组或类似的)对象提供分配和解除分配接口。

类的最小复制:

template <typename T>
struct other_t{
    // members, but none of them are pointer member
    other_t(){}// ctor with argument in reality
    static buffer_t buffer;
};

struct foo{
    other_t<some_T>* ptr;
    foo(const foo&)=delete;
    foo& operator=(const foo&)=delete;
    //~foo{ delete ptr; } //originally
    ~foo{ other_t<some_T>::buffer.deallocate(ptr); }; // actually
};

PS:我对汇编语言不熟悉,如果以上还不足以解决问题,请在下方评论,谢谢。

【问题讨论】:

  • 这是真正的编译器输出吗? mov rcx,qword ptr [rsp+108h] 后跟 mov rcx,qword ptr [this] 似乎不太可能。 (第二条指令覆盖第一条的唯一结果)。
  • @PeterCordes 我直接从 Visual Studio 的Disassembly 窗口复制,并在调试模式下编译。
  • 好的,调试模式(未优化)解释了带有冗余指令的braindead asm。它比优化的 asm 更难阅读。
  • @YiFei 它调用了我自己的类的一些标量删除析构函数 -- 我们需要看到这个“自己的类”。这些“标量删除析构函数”错误可能由于多种原因而发生。一个无效的实例,一个没有正确复制语义的类,等等。另外,你为什么要研究汇编代码?为什么不查看您的 C++ 源代码中的错误?
  • @oldrinb -- 这就是您使用 C++11 关闭复制的方式。

标签: c++ visual-studio assembly destructor


【解决方案1】:

标量删除析构函数在 Visual Studio 中是不是一个独特的概念,也就是说,它在任何其他编译器中都看不到?

是的。

在什么情况下,调用我的自定义析构函数后,上面的代码会将 eax 设置为 1?

rbp + 0x0E8处的4字节值的低位为1的情况。

你的实际问题是什么?

【讨论】:

  • 感谢您的快速响应,那最终会进行双重删除吗?关于我的实际问题,无论如何它都是一个崩溃,我会解决它......
  • @YiFei “那最终会做双重删除吗?” - 谁知道?我不。尤其是没有看到源代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-21
  • 2020-04-06
  • 2010-10-17
  • 2017-07-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多