为什么虚拟方法生成对_sbrk的未定义引用?

Why virtual method generates undefined reference to _sbrk?

本文关键字:sbrk 未定义 引用 虚拟 方法 为什么      更新时间:2023-10-16

这编译得很优雅:

class dummy {
};

这抱怨对_sbrk的未定义引用:

class dummy {
virtual ~dummy();
};

为什么虚拟方法生成对_sbrk的未定义引用?

我曾经认为vtable是静态分配的,不需要malloc.

编译器:arm-none-eabi-gcc 8.0.0最近newlib。用-fno-rtti -fno-exceptions -fno-unwind-tables编译。

测试程序(bootmain(:

class base {
public:
virtual ~base();
};
class dummy : public base {
public:
~dummy();
};
base::~base() {
__BKPT();
}
dummy::~dummy() {
__BKPT();
}
extern "C" void _sbrk() {
__BKPT();
}
void boot() {
for(;;) {
base b;
dummy d;
}
return 0;
}

派生类可以有自己的删除运算符。此功能很少使用 - 根据我的经验,几乎从未使用过。虚拟析构函数允许在使用删除表达式时调用正确的运算符 delete(delete p(。

编译器肯定生成了一个虚拟析构函数与删除,它调用特定于类的运算符 delete,在几乎所有情况下,它恰好是全局运算符 delete (::operator delete(,但它也可以被类中定义的本地运算符覆盖。

由于从未使用过delete(对于该特定类型(,因此永远不会调用带删除的析构函数自动生成的虚拟函数,但仍在 vtable 中引用该函数。除非你有适当的编译器和链接器支持,否则每个虚拟函数都会在 vtable 中引用,并且 vtable 至少在类的构造函数中使用,因此任何构造的对象都需要该类的每个虚函数。

如果您有适当的链接器支持,则只能引入已命名的虚拟函数;其他 vtable 条目可以为 null,因为它们永远不会被引用。

编辑:标准报价

来自[class.dtor]/12:

定义虚拟析构函数时(包括 隐式定义(,非数组释放函数是 确定为表达式delete this出现在析构函数类的非虚拟析构函数(请参见 [expr.delete](。 如果查找失败或解除分配函数已删除 定义,程序格式不正确。[ 注意:这可确保 与对象的动态类型对应的释放函数 可用于删除表达式 ([class.free](。— 尾注 ]

"虚拟析构函数"...确定为"非虚拟析构函数"包含表达式是一个可能难以解码的措辞:既然析构函数是虚拟的,我们为什么要谈论虚拟析构函数?(我不得不多次阅读上面的标准文本。

另一种查看方式是,就可能的实现而言(一些实现曾经正是这样做的(:

每个析构函数实际上都deallocate一个布尔参数,编译器添加代码:

if (deallocate)
delete this;

就在析构函数主体结束之前,所有析构函数都使用false参数调用,但在调用delete运算符时,除了完整对象的一个。

我几乎可以肯定_sbrk被调用是因为您对虚函数的使用已经拉入了代码路径,以引发对"纯虚函数"调用的异常。

在你的地图文件中四处看看什么在叫_sbrk,什么在叫它,等等,直到你找到根。

请参阅这篇文章以获取更多信息:声明抽象类(纯虚拟方法(大大增加了二进制大小

附言:虚拟表不需要动态内存分配

我曾经认为 vtable 是静态分配的,不需要 malloc。

不确定你从哪里得到这个假设:

  • 不能保证如何实现 vtables,标准也没有说动态调度需要使用 vtables 来实现。这只是最常见的方法。
  • 在您的问题中,我缺少任何证据,证明_sbrk实际上是因为析构函数正在被拉入virtual

而且,我真的不明白这个问题的目的:如果你想使用虚拟方法,无论如何你都需要有动态内存分配函数。