2

这可以优雅地编译:

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

测试程序(boot就像main):

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;
}
4

3 回答 3

3

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

编译器当然生成了一个虚拟的destructor-with-delete,它调用类特定的运算符delete,在几乎所有情况下,它恰好是全局运算符delete( ::operator delete),但也可以被类中定义的局部运算符覆盖。

因为delete从未使用过(对于该特定类型),所以永远不会调用 destructor-with-delete 自动生成的虚函数,但它仍然在 vtable 中引用。除非您有适当的编译器和链接器支持,否则每个虚函数都在 vtable 中引用,并且 vtable 至少在类的构造函数中使用,因此任何构造的对象都将需要该类的每个虚函数。

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

编辑:标准报价

[class.dtor] /12

虚拟析构函数的定义点(包括隐式定义),非数组释放函数被确定为出现在析构函数类的非虚拟析构函数中的表达式delete this(参见 [expr.delete])。如果查找失败或释放函数有一个已删除的定义,则程序是非良构的。[注意:这确保了与对象的动态类型相对应的释放函数可用于删除表达式([class.free])。——尾注]

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

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

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

if (deallocate)
  delete this;

false就在析构函数体的末尾之前,并且在调用运算符时,除了完整对象之一之外,所有析构函数都使用参数delete调用。

于 2017-08-20T04:48:59.340 回答
-1

我几乎可以肯定 _sbrk 正在被调用,因为您对虚拟函数的使用已经引入了一条代码路径,以引发对“纯虚拟函数”的调用的异常。

去看看你的地图文件,看看是什么在调用_sbrk,是什么在调用它,等等,直到你找到根。

请参阅这篇文章了解更多信息: Declaring abstract class (pure virtual method)显着增加二进制大小

ps vtables 不需要动态内存分配

于 2017-08-19T22:51:42.790 回答
-2

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

不知道你从哪里得到这个假设:

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

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

于 2017-08-19T13:16:00.207 回答