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

时间:2019-02-01 14:42:44

标签: c++ memory-management windbg virtual-functions

(关于软件设计,这是一个很大的问题。如果不适合StackOverflow,我愿意将其复制到Software-Engineering社区)

我正在使用heap_stat这个脚本来调查转储。该脚本基于以下思想:对于具有虚拟功能的任何对象,vftable字段始终是第一个字段(允许查找该对象类的内存地址)。

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

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

创建一个具有虚函数的废话类,然后让我的类从该废话类继承:

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

class Own_Class : public NONSENSE, ...

这正如预期的那样,在符号中创建了一个vftable条目,我可以找到它(使用Windbg的{​​{1}}命令):

x /2 *!Own_Class*vftable*

我还看到了内存使用的不同:

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

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

有一个陷阱:显然,很多对象定义为:

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

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具有以下影响:

不被考虑:

  • 堆栈上还有一个调用,这仅在系统已经达到其内存使用限制的情况下才有影响。 (我不知道链接器如何指向特定方法)
  • CPU使用率增长将很小,无法看到。
  • 速度下降太小了,看不到。

要考虑在内:

  • 如前所述,每个对象的应用程序内存使用量增加了8个字节。如果常规对象的大小约为1000字节,则意味着内存使用量增加了±1%,但是对于内存大小小于80字节的对象,这可能导致内存使用量增加了+10%。

现在我有以下问题:

  1. 我对影响的分析正确吗?
  2. 是否有更好的方法来强制创建vftable字段,而影响较小?
  3. 我想念什么吗?

预先感谢

2 个答案:

答案 0 :(得分:0)

  

我对影响的分析正确吗?

不。 __declspec(novtable)省略了给定类的 vtable本身的生成, vtable 的指针仍然存在,因此sizeof不会改变。

__declspec(novtable)用于具有派生类的基类。这样派生类的构造函数会将vtable指针设置为派生vtable,而不需要基本vtable。

因此,此优化消除了一个指针分配(在构造函数代码的生成部分中),并为vtable本身留了一些空间。进行每个对象优化对您的目标不是很有用,因为它只能进行小的每个类优化。

如果您不自行创建基础实例,并且不在构造函数/析构函数中调用虚拟方法,则此方法将起作用。

通过使虚拟函数成为非虚拟函数来省略虚拟函数调用是完全独立的故事。它称为去虚拟化。当编译器可以确定使用哪个类的实例时,它将用非虚拟调用替换虚拟调用。

__declspec(novtable)仍然无法帮助实现虚拟化。 final / sealed关键字可能有助于取消虚拟化,因为它们说没有其他派生的类/方法。

关于vtable指针是第一个成员的假设,这可能是错误的。如果您的基类没有vtable,但是有一些数据成员,则vtable指针将不是第一个。另外,vtable指针可能不止一个。

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

由于使用heap_stat脚本的方法本质上受到限制,因此我建议使用UMDH进行堆分析,因为它根本不依赖于vtable,并且可以显示各种对象

答案 1 :(得分:0)

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

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

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

在将所有析构函数声明为虚拟之后,我打算进行一些性能测试(我相信将方法声明为虚拟可能会影响速度,因为方法声明已更改,尤其是对于服务器应用程序重载),我将使该帖子保持最新状态。