在实践中,C++ vtable 方法是如何排序的?

8
理论上,C ++没有二进制接口,并且vtable中方法的顺序未定义。更改类的定义中的任何内容,您需要重新编译每个依赖它的类,在每个dll等中都需要重新编译。
但我想知道的是编译器在实践中如何工作。我希望他们只是使用头文件/类中定义的方法顺序,这将使附加其他方法变得安全。但是他们也可以使用mangled名称的哈希来使它们独立于顺序,但也完全无法升级。
如果人们对特定版本的特定编译器在不同操作系统中的工作方式有具体的了解,那将非常有帮助。
补充:理想情况下,链接器符号将为虚拟方法偏移量创建,以便偏移量永远不会被硬编码到调用函数中。但我的理解是从来没有这样做。正确吗?

我需要检查以确保它们按照声明的顺序排列,但我相当确定它们是这样的。如果您派生一个子类并添加更多虚函数,则它们肯定会在基类函数之后出现。 - tukra
<removed and made into answer> - tukra
不仅虚函数表中方法的顺序,而且虚函数表的存在本身都是实现细节。标准只规定了虚函数,而没有规定它们的实现 :-) - Serge Ballesta
4个回答

3

看起来微软的VTable可能会被重新排序。

以下内容摘自https://marc.info/?l=kde-core-devel&m=139744177410091&w=2

我(Nicolas Alvarez)可以确认这种行为确实发生了。

我编译了这个类:

struct Testobj {
    virtual void func1();
    virtual void func2();
    virtual void func3();
};

有一个程序调用 func1(); func2(); func3();

然后我在 结尾处 添加了一个带有 int 参数的 func2() 重载函数:

struct Testobj {
    virtual void func1();
    virtual void func2();
    virtual void func3();
    virtual void func2(int);
};

重新编译了类,但没有使用该类重新编译程序。

调用func1(); func2(); func3(); 的输出结果为

This is func1
This is func2 taking int
This is func2

这表明如果我声明 func1() func2() func3() func2(int),虚函数表的布局将为 func1() func2(int) func2() func3()。
经过 MSVC2010 的测试。

我遇到了相同的问题。你有什么想法吗? - ldlchina

2
在MSVC 2010中,它们按照你声明的顺序排列。我想不出其他编译器会以不同的方式处理,尽管这是一种任意的选择。它只需要保持一致即可。它们只是指针数组,所以不用担心哈希或混淆。
无论顺序如何,派生类中添加的额外虚函数必须在基类之后,否则多态转换将无法正常工作。

MSVC 没有太多选择,因为它的 vtable 布局必须匹配 COM 约定。 - MSalters

1
据我所知,这些方法的顺序总是按照声明的顺序排列的。这样,您就可以始终在最后添加新的虚拟方法(或在所有先前声明的虚拟方法下方添加)。如果您从中间删除任何虚拟方法或添加新的虚拟方法 - 您确实需要重新编译和重新链接所有内容。 我确定这一点-我已经犯过这个错误。根据我的经验,这些规则适用于MSVC和GCC。

0

任何编译器都必须至少将特定类的所有可行条目放在一起,派生类的条目要么在前面,要么在后面,也要在一起。

实现这一点最简单的方法是使用头文件顺序。很难理解为什么任何编译器会做出不同的事情,因为它需要更多的代码、更多的测试等,只会提供另一种错误发生的方式。我看不到任何可辨认的好处。


如果同一类的不同头文件中方法的顺序不同,那么使用一个混淆名称的哈希值就可以解决这个问题。根据标准,方法出现的顺序不应该有关系(我猜)。虽然我认为这不是一个好主意,但这是使用不同顺序的理由。 - Tuntable
1
如果同一类的不同头文件中方法的顺序不同,这将违反一个定义规则。因此,结果是未定义的行为! - Klaus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接