在 windbg "x /2" 结果中强制执行 vftable 条目,需要考虑什么?

Enforcing a vftable entry in windbg "x /2" results, what to consider?

(这是一个关于软件设计的大问题。如果它不适用于 Whosebug,我愿意将其复制到软件工程社区)

我正在使用 heap_stat,一个调查转储的脚本。这个脚本基于这样的想法,对于任何具有虚函数的对象,vftable 字段总是第一个(允许找到对象的 class 的内存地址)。

在我的应用程序中有一些对象有 vftable 个条目(通常每个 STL 个对象都有),但也有相当多的对象没有。

为了强制出现 vftable 字段,我做了以下测试:

创建一个废话class,有一个虚函数,让我的class继承这个废话class:

class NONSENSE {
    virtual int nonsense() { return 0; }
};

class Own_Class : public NONSENSE, ...

正如预期的那样,这在符号中创建了一个 vftable 条目,我可以找到它(使用 Windbgx /2 *!Own_Class*vftable* 命令):

00000000`012da1e0 Own_Application!Own_Class::`vftable'

我还看到了内存使用的差异:

sizeof(an normal Own_Class object) = 2928
sizeof(inherited Own_Class object) = 2936

=> 已为此对象添加 8 个字节。

有一个问题:显然有相当多的对象被定义为:

class ATL_NO_VTABLE Own_Class

ATL_NO_VTABLE 阻止创建 vftable 条目,这意味着以下内容(ATL_NO_VTABLE 等于 __declspec(novtable)):

// __declspec(novtable) is used on a class declaration to prevent the vtable
// pointer from being initialized in the constructor and destructor for the
// class.  This has many benefits because the linker can now eliminate the
// vtable and all the functions pointed to by the vtable.  Also, the actual
// constructor and destructor code are now smaller.

在我看来,这意味着 vftable 不会被创建,因为对象方法被更直接地调用,对方法执行和堆栈处理的速度有影响。允许创建 vftable 具有以下影响:

不予考虑:

要考虑:

现在我有以下问题:

  1. 我对影响的分析是否正确?
  2. 有没有更好的方法强制创建 vftable 字段,影响更小?
  3. 我错过了什么吗?

提前致谢

Is my analysis on the impact correct?

没有。 __declspec(novtable) 省略了给定 class 的 vtable 本身 的生成,指向 vtable 的 指针仍然存在,因此 sizeof 将不变。

__declspec(novtable) 用于派生 classes 的基础 classes。因此派生的构造函数 class 会将 vtable 指针设置为派生 vtable,不需要基 vtable。

因此,此优化消除了一个指针赋值(在构造函数代码的生成部分),以及 vtable 本身的一点 space。 per-object 优化对你的目标不是很有用,因为它只做小的 per-class 优化。

如果您不自己创建基础实例,并且不在 constructor/destructor 中调用虚拟方法,它就可以工作。

通过使它们成为非虚函数来省略虚函数调用是完全不同的故事。它被称为去虚拟化。当编译器可以确定使用了 class 的实例时,它会将虚拟调用替换为非虚拟调用。

__declspec(novtable)无论如何都无法帮助去虚拟化。 final / sealed 关键字可能有助于去虚拟化,因为他们说没有进一步派生 classes/methods。

关于 vtable 指针是第一个成员的假设,这可能是错误的。如果你的基础 classes 没有 vtable,但有一些数据成员,vtable 指针将不是第一个。也可能有不止一个虚表指针。

要以编程方式分析转储中的结构,我建议使用适当的 API。有两个 API:DIA SDK and dbghelp functions。它们很相似,但第一个是基于对象 (COM) 的,第二个是平面的 API,所以第一个可能更容易使用。

由于 heap_stat 脚本的方法存在固有的局限性,我建议堆分析使用 UMDH,它根本不依赖 vtable,并显示各种对象

与此同时,我发现了一种非常简单的方法来强制每个 class 的 vftable' 条目:只需将每个析构函数声明为虚拟的。

为了找到所有还不是虚拟的析构函数,我在开发目录中的 Ubuntu 应用程序中启动了以下命令:

find ./ -name "*.h" -exec fgrep "~" {} /dev/null \; | grep -v "virtual"

在将所有析构函数声明为虚函数后,我打算做一些性能测试(我认为将方法声明为虚函数可能会对速度产生影响,因为方法声明已更改,尤其是对于负载较重的服务器应用程序),我会post 保持最新。