【问题标题】:Why does the deleting destructor occupy a second vtable slot besides the ordinary destructor?为什么删除析构函数除了普通析构函数还要占用第二个vtable槽?
【发布时间】:2020-06-09 13:20:24
【问题描述】:

在以Itanium C++ ABI 为模型的 C++ ABI 实现中,随后是许多其他处理器的 ABI,虚拟析构函数实际上占用了两个 vtable 插槽。除了执行你所期望的“完整对象析构函数”之外,还有第二个条目用于“删除析构函数”,它调用第一个,然后删除对象的内存。

这种方法存在一个问题,这在小型内存系统中可能会造成麻烦:动态内存管理器已链接,即使没有其他代码使用它。当应用程序中的任何地方都没有调用删除时,这是死代码。这是因为 C++ 编译器/链接器通常无法检测到 vtable 中的插槽未被从任何地方调用,因此无法删除相关代码。显然,如果删除析构函数可以以不涉及 vtable 条目的不同方式实现,并允许编译器/链接器省略这个死代码,那会更好。

当然可以实现自定义void operator delete(void *) {} 来防止链接器引入动态内存代码,但这仍然不能阻止删除析构函数代码完全发出。

因此我的问题是:有没有更好的方法来实现删除析构函数?我的想法是返回指向内存块开头的指针,以便从完整的对象析构函数中删除。如果要在销毁后删除内存块,则返回的地址可以由调用operator delete的非虚拟函数使用。本质上,让完整的对象析构函数返回内存地址将允许删除析构函数是非虚拟的,因此有资格消除死代码。

但我想我一定忽略了一些东西,这使得这个相当简单的解决方案变得不可能。但那会是什么?有人可以为我解释一下 Itanium ABI 中的设计决策吗?

编辑:我找到了提供部分答案的信息here

上面的答案包含这样的解释:

当某个类定义了自己的操作符 delete 时,选择要调用的特定操作符 delete 就像从类析构函数内部查找一样。这样做的最终结果是,对于具有虚拟析构函数的类,delete 的行为就好像它是一个虚函数(尽管形式上是该类的静态成员)。

显然,Itanium API 选择使其表现像一个虚函数的方式是使调用它的析构函数成为一个实际虚函数。 p>

但是,这不是实现它的唯一方法。链接文章的中心是一个使用带有隐藏参数的单个虚函数的实现,但该解决方案会产生与我在上面描述的相同的不良行为。如果类有自定义实现,则另一个实现可能是让完整的对象析构函数返回运算符 delete() 的地址,否则返回 nullptr。这样可以避免我上面描述的问题。

所以,在稍加修改的形式中,我的问题仍然存在。

【问题讨论】:

  • 这不是一个重复,但仍然感谢您的链接。我不是问为什么有多个析构函数,而是为什么删除析构函数需要是虚拟的,这会产生一些不良后果,如上所述。

标签: c++ memory-management linker virtual-destructor itanium


【解决方案1】:

与销毁类时的普通虚函数不同,必须以正确的顺序调用其析构函数和其所有父级的析构函数。

技术上可以将所有函数调用合并为一个函数调用,但需要了解每个析构函数的实现或对象的完整结构。基本上是对通话进行去虚拟化。编译器并不擅长。不太确定所有细节,因为考虑到所有可能的过度虚拟对象,这是一个相当复杂的问题。

【讨论】:

  • 删除析构函数调用完整对象析构函数,然后调用operator delete()。所以它是完整的对象析构函数来完成调用子对象的析构函数的所有工作。我看不出删除析构函数与此有什么关系。据我所知,将删除析构函数设为虚拟的原因是必须找到正确的地址才能将其传递给 operator delete(),并且只能由正在被销毁的(派生)类确定。但是如果完整的对象析构函数返回地址,问题就消失了,对吧?
  • @sh- 没有完整的对象析构函数只调用行中的下一个,并且每个只知道要调用的下一个。当某些析构函数的实现对最终派生类隐藏时,​​如何组织它?
  • 编译器在编译完整的对象析构函数时知道所有的基类和子对象,所以它知道所有的析构函数。但是,您在某一方面是对的:由于基类析构函数调用其自己的基类析构函数,派生类析构函数不遵循派生链,而仅调用其自己的直接基类的析构函数。
  • @sh- 如果实现隐藏在不同的 .cpp 中,编译器不知道构造函数或析构函数。此外,它可能是一个不同的编译器版本/品牌,它构建了构造函数/析构函数的实现并以不同的方式组织事物——但如果它们遵循关于 ABI 的相同基本规则,它仍然可以工作。也许,你想要的是可能的,但它限制了 ABI 兼容性。另外,为什么要担心虚拟类需要的内存量呢?首先,您不应该创建太多虚拟类,模板会提供更好的解决方案。
  • 编译器不知道构造函数或析构函数的代码,但它知道它们的名称,因此知道如何调用它们。我不明白为什么编译器需要为我感兴趣的任何内容查看构造函数/析构函数的代码。请注意,我并没有尝试将所有析构函数代码内联到单个函数中。我试图找出为什么删除析构函数需要在 vtable 中。
猜你喜欢
  • 2019-01-29
  • 2012-03-13
  • 2015-10-19
  • 2011-03-23
  • 2015-01-12
  • 2012-08-11
  • 2013-04-27
  • 2017-04-06
相关资源
最近更新 更多