【问题标题】:Why does a virtual destructor write to memory?为什么虚拟析构函数写入内存?
【发布时间】:2021-07-17 07:37:25
【问题描述】:

最近,在使用自定义分配器代码和放置 new+delete 时,我注意到一些令我惊讶的事情:当调用虚拟析构函数时,它会写入对象即将释放的内存。

这是为什么呢?

(更新) 除此之外:我对这里的实际行为更感兴趣,而不是 C++ 标准必须说的内容,我确定没有具体说明这种行为。

这里有一个小程序来演示:

#include <new>
#include <cstring>
#include <iostream>

using std::cout;
using std::endl;

struct Interface {
    virtual ~Interface() = default;
};

struct Derived : public Interface {
};

alignas(Derived) unsigned char buffer[sizeof(Derived)];

int main() {

    memset(buffer, 0xff, sizeof(buffer));
    cout << "Initial first byte: 0x" << std::hex << (int)buffer[0] << endl;
    
    // Create an instance, using 'data' as storage
    Derived *pDer = ::new (buffer) Derived();
    cout << "After ctor, first byte: 0x" << std::hex << (int)buffer[0] << endl;
    
    pDer->~Derived();
    
    cout << "After destroy, first byte: 0x" << std::hex << (int)buffer[0] << endl;

    return 0;
}

直播链接:https://godbolt.org/z/jWv6qs3Wc

这是输出:

Initial first byte: 0xff
After ctor, first byte: 0x68
After destroy, first byte: 0x88

如果我删除虚拟的Interface,那么内存就不会像预期的那样发生变化。

这是某种调试功能吗?

它似乎是特定于编译器的。 Clang 不会这样做,但 GCC 会。

-O2 似乎确实消失了。但是,我仍然不确定它的目的。

【问题讨论】:

  • 我怀疑标准对此有什么可说的(可能的 UB 除外?)。更有可能是编译器实现细节的问题。
  • 调试编译的常见做法是使用位模式覆盖已释放或未初始化的内存 A) 使用时可能会导致崩溃; B) 协助识别其他问题,例如缓冲区溢出。
  • 您的程序通过访问不再存在的对象导致未定义的行为(new 导致字符数组中字符的生命周期结束)
  • 另外,Derived 的缓冲区可能未正确对齐
  • 所有标准要求是调用析构函数会结束受影响对象的生命周期。除了析构函数明确完成的任何事情外,该标准既不要求也不阻止实现修改对象占用的内存。奇怪的是,您看到的是编译器提供的用于帮助调试的内容,或者与对象相对于您提供的缓冲区的对齐相关的内容。

标签: c++ gcc virtual-destructor gcc4


【解决方案1】:

要销毁Derived,概念上调用Derived::~Derived(在这种情况下不执行任何操作),然后调整vtable 以使对象为Interface,然后调用Interface::~Interface。您所观察的是指向Interface vtable 的指针(如here 所示,构造Interface 给出相同的第一个字节)。

如果您启用优化,那么由于 Interface::~Interface 什么都不做,Derived::~Derived 也可以优化为无操作,并且您会看到打印的第一个字节相同。

【讨论】:

    猜你喜欢
    • 2015-11-12
    • 2012-09-02
    • 2019-11-09
    • 2011-10-29
    • 2011-02-04
    • 2014-02-12
    • 2010-12-11
    • 1970-01-01
    • 2013-09-04
    相关资源
    最近更新 更多