代码之家  ›  专栏  ›  技术社区  ›  Dominique

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

  •  1
  • Dominique  · 技术社区  · 7 年前

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

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

    在我的应用程序中,有一些对象 VFABLE 条目(通常每 STL 但也有很多物体没有。

    为了迫使 VFABLE 菲尔德,我做了以下测试:

    创建一个无意义类,具有一个虚拟函数,并让我的类继承这个无意义类:

    class NONSENSE {
        virtual int nonsense() { return 0; }
    };
    
    class Own_Class : public NONSENSE, ...
    

    正如所料,这创造了 VFABLE 在符号中输入,我可以找到(使用 Windbg x /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 阻止创建 VFABLE 条目,意思是( ATL U不可用 等于 __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.
    

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

    不考虑:

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

    考虑:

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

    现在我有以下问题:

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

    提前谢谢

    1 回复  |  直到 7 年前
        1
  •  0
  •   Alex Guteniev    7 年前

    我对影响的分析正确吗?

    不。 __declspec(novtable) 忽略生成 vtable本身 对于给定的类, 指向vtable的指针 仍然存在,所以大小不会改变。

    _ DECLSPEC(不可用) 用于具有派生类的基类。所以派生类的构造函数将vtable指针设置为派生vtable,而不需要基vtable。

    因此,这种优化消除了一个指针分配(在构造函数代码的生成部分),以及vtable本身的一点空间。对你的目标没有多大用处 每个对象 优化,因为它只做小的 每班 优化。

    如果您不自己创建基实例,也不在构造函数/析构函数中调用虚方法,那么它将起作用。

    通过使虚函数调用成为非虚函数而省略虚函数调用是完全不同的。它叫 非理想化 . 当编译器可以确定使用哪个类的实例时,它用非虚调用替换虚拟调用。

    _ DECLSPEC(不可用) 无论如何都帮不上脱离现实。 final / sealed 关键字可能有助于去虚拟化,因为它们说没有进一步的派生类/方法。

    关于vtable指针是第一个成员的假设,这可能是错误的。如果基类没有vtable,但是有一些数据成员,那么vtable指针将不是第一个指针。也可能有多个vtable指针。

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

    由于heap_stat脚本的方法固有的局限性,我建议使用heap分析 UMDH 相反,它完全不依赖vtable,并显示各种对象

        2
  •  0
  •   Dominique    7 年前

    同时,我找到了一个非常简单的方法 vftable' 每个类的条目:只需将每个析构函数声明为virtual。

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

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

    在将所有析构函数声明为virtual之后,我计划进行一些性能测试(我相信将一个方法声明为virtual可能会影响速度,因为方法声明已经更改,特别是对于一个负载很重的服务器应用程序),我将继续这篇文章日期。