C ++虚拟继承,虚拟析构函数和dynamic_cast <void *>

Tdashroy

我试图理解为什么第二个断言在以下代码中失败(以防万一,请通过Visual Studio 2019使用MSVC):

#include <cstdlib>

class grandparent
{
public:
    virtual ~grandparent() {};
};

class parent : public virtual grandparent 
{};

class child : public parent
{};

int main() 
{
    void* mem = malloc(sizeof(child));
    child* c = new (mem) child;
    assert(dynamic_cast<void*>(c) == mem); // ok
    std::destroy_at(c);
    assert(dynamic_cast<void*>(c) == mem); // fails
}

根据我的理解,dynamic_cast<void*>指向多态类型的指针返回该指针的最派生类型的地址。这在通过销毁之前效果很好std::destroy_at但是销毁之后,它不再将指针分配给最初分配的内存,但是我不明白为什么。

所以我的问题是:

  1. 如何dynamic_cast<void*>获取给定指针的最派生类型的地址?
  2. 析构函数如何做才能改变dynamic_cast<void*>返回值?

在四处搜寻并尝试自我教育时,我发现了有关vtables系列的以下博客文章:https : //shaharmike.com/cpp/vtable-part4/在博客文章中,有一些关于析构函数的简短提及:

这是一个快速思考的练习:为什么析构函数将vtable指针更改为指向自己类的指针,而不是使其始终指向具体类?答:因为到析构函数运行时,所有继承类都已被销毁。调用此类的方法不是您想要执行的操作。

由此,我有一个猜测是dynamic_cast<void*>可以通过查看给定指针的vtable来工作,并且就像引用中所说的那样,析构函数在调用它们时会更改vtable指针。这个对吗?如果是这样,我想确切了解其下发生了什么,因此,希望您对此有任何解释或参考,以供进一步阅读。

杰弗里

您在问它是如何工作的。您将无法获得确切的答案。最好的答案是,std::destroy_at如果它是UB至,则将能够权威地回答dynamic_cast这就是标准所说的,只要遵守标准,编译器作者就可以按照自己的喜好来做。

就是说,您可以在实践中推断可能发生的情况。

std::destroy_at(c);

不会取消分配内存。它只能修改由malloc分配的内存。

dynamic_cast

从逻辑上讲应该只看内存缓冲区。缺少局部优化,它唯一可以看到的地方是malloc分配的缓冲区。

这为您提供了一些工具来弄清楚您的实现在做什么。新放置后立即在该位置打印sizeof(child)出内存字节c然后在每个步骤之后再次打印内存。

这将告诉您您的实现正在做什么。它不能保证其他编译器/计算机可以做什么。但是我想您会在销毁期间看到v表指针被重置为零。

如果我在编译器上尝试以下操作:

#include <cstdlib>
#include <cassert>
#include <memory>
#include <iostream>

class grandparent
{
public:
virtual ~grandparent() {};
};

class parent : public virtual grandparent 
{};

class child : public parent
{
int x = 0x42;// just to see where this is in memory
};

void print(void* c, size_t size)
{
for (size_t i = 0; i < size; i++)
{
    std::cout << std::hex << (int)(((unsigned char*)c)[i]) << " ";
}
std::cout << std::endl;
}

int main() 
{
void* mem = malloc(sizeof(child));
child* c = new (mem) child;

print(c, sizeof(*c));

assert(dynamic_cast<void*>(c) == mem); // ok
std::destroy_at(c);

print(c, sizeof(*c));

assert(dynamic_cast<void*>(c) == mem); // fails
}

我得到:

60 ab d7 0 42 0 0 0 58 ab d7 0

60 ab d7 0 42 0 0 0 34 ab d7 0

断言失败:dynamic_cast(c)== mem,文件

d:\ projects \ test1 \ test1.cpp,第41行

因此,在对象之前还有一些其他内容。这里有0x42,三个填充零和一个指针的存储(这是v表,如调试器所示)。一个指针,在删除过程中会被修改,并阻止下一个dynamic_cast工作。然后可以检查内存并找出更多内容。

但是,您所发现的任何东西都不能保证保留在另一个实现中,甚至在您的下一个版本中也不会保留。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章