2

精简版

我正在使用特定对象的 vtable 中的条目来调用从接口继承的虚拟方法。最后,我正在寻找一种方法来获取每个地址到虚拟方法在特定对象的 vtable 中的确切偏移量。

详细版本


免责声明

我知道这个主题是依赖于实现的,并且不应该尝试手动执行此操作,因为编译器会执行(正确的)工作,并且 vtable 不被视为标准(更不用说数据格式了)。我在此作证,我已经阅读了数十篇“不要这样做......只是,不要这样做!”并且清楚我的行为可能产生的令人发指的后果。

因此(并有利于进行建设性的讨论)我将使用g++ (4.x.x)Linux x64 平台上的编译器作为我的参考。使用以下代码编译的任何软件都将使用相同的设置,因此就这一点而言,它应该是平台无关的。


好的,我的这整个问题完全是实验性的,我不想在生产代码中使用它,而是为了教育我自己和我的同学(我的教授问我是否可以写一篇关于这个主题的快速论文)。

我要做的基本上是使用偏移量自动调用方法来确定调用哪个方法。类的基本大纲如下(这是一个简化,但显示了我当前尝试的组成):

class IMethods
{
    virtual double action1(double) = 0;
    virtual double action2(double) = 0;
};

正如您所看到的,只有一个具有共享相同签名的纯虚拟方法的类。

enum Actions
{
    actionID1,
    actionID2
};

枚举项用于调用适当的方法。

class MethodProcessor : public IMethods
{
    public:
        
        double action1(double);
        double action2(double);
};

故意省略了上述类的ctor/dtor。我们可以放心地假设这些是从接口继承的唯一虚方法,并且多态性是无关紧要的。


基本概要到此结束。现在进入真正的主题:

是否有一种安全的方法可以将 vtable 中的地址映射到继承的虚拟方法?

我想做的是这样的:

MethodProcessor proc;
size_t * vTable = *(size_t**) &proc;
double ret = ((double(*)(MethodProcessor*,double))vTable[actionID2])(&proc, 3.14159265359);

这工作正常并且正在调用action2,但我假设指向的地址等于索引 1 并且这部分让我感到困惑:如果在定义action2地址之前在 vtable 中添加了某种偏移量怎么办?action1

在一本关于 C++ 数据对象模型的书中,我读到通常 vtable 中的第一个地址通向RTTI (runtime type information),作为回报,我无法确认,因为vTable[0]它合法地指向action1.

编译器知道每个虚拟方法指针的确切索引,因为,是的,编译器正在构建它们并用增强代码替换虚拟方法的每个成员调用,该代码等于我上面使用的代码 - 但知道要使用的索引。我这一次接受了有根据的猜测,即在我定义的虚拟方法之前没有偏移量。

我不能使用一些 C++-Hack 让编译器在编译(甚至运行)时计算正确的索引吗?然后我可以使用这些信息为我的枚举项添加一些偏移量,而不必担心投射错误的地址......

4

2 回答 2

1

Linux 使用的 ABI 是众所周知的:您应该查看Itanium ABI 的虚拟功能表布局部分。该文档还指定了查找 vtable 的对象布局。但是,我不知道您概述的方法是否有效。

请注意,尽管指向文档,但我建议使用该信息来根据它来玩花样!不幸的是,您没有解释您的实际目标是什么,但似乎使用指向irtual函数成员的指针是一种更可靠的运行时分派到虚函数的方法:

double (IMethods::*method)(double)
    = flag? &IMethods:: action1: &INethods::actions2;
MethodProcessor mp;
double rc = (mp.*method)(3.14);
于 2013-09-28T07:09:58.127 回答
0

我有几乎同样的问题,更糟糕的是,就我而言,这是针对生产代码的......不要问我为什么(告诉我:“不要这样做......只是,不要这样做” ,或者至少使用现有的动态编译器,例如 LLC ...)。

当然,如果要求编译器设计者遵循一些通用的 C++ ABI 规范(或至少指定他们自己的规范),这种“折磨”可能会得到缓解。显然,对于遵守“通用 C++ ABI”(通常也称为“ITANIUM C++ ABI”,Dietmar Kühl 提到)的共识越来越多。

因为您使用的是 G++ 并且这是出于教育目的,所以我建议您查看“-fdump-class-hierarchy”g++ 开关的作用(您会发现 vtable 布局);这是我个人用来确保我没有投错地址的方法。


注意:使用 x86 (ia32) MinGW g++-4.7.2 编译器,

double MethodProcessor::action( double )中,隐藏的“(MethodProcessor*)this”参数将在寄存器 (%ecx) 中传递。

而在 中((double(*)(MethodProcessor*,double)),第一个显式的“this”参数将在堆栈上传递。

于 2013-11-06T09:09:02.817 回答