【发布时间】:2016-12-13 17:38:02
【问题描述】:
我正在使用 Visual Studio 2015 构建一些单线程 C++ 项目。
我有一个非常粗糙的池,项目的其余部分会获取内存并返回给它。它在return 0; 之前运行没有问题。在那之后,我知道有一些编译器生成的退出代码,其中它调用了我自己的类的一些标量删除析构函数(下面的class foo)。
这是标量删除析构函数的一些汇编代码(我用aaaaa 和foo 隐藏了这些类和命名空间的名称):
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的调用.
但是在这些行的最后一击中,eax 在mov 之后被设置为1,然后执行对operator delete 的调用,并且恰好在该行它在@987654333 处触发了一个断点@。
所以我的问题是:
- 在 Visual Studio 中标量删除析构函数是一个独特的概念,也就是说,它在任何其他编译器中都没有看到吗? (这在 immibis 的回答中得到了完美的回答,是的)
- 在什么情况下,调用我的自定义析构函数后,上面的代码会将
eax设置为1?为什么编译器会这样,也就是原来要删除的对象operator delete是什么? -
operator delete的执行是双重删除吗? - 我必须追溯到
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