如何使用虚拟表正确调用成员方法?

时间:2013-09-28 02:05:40

标签: c++ pointers methods g++ vtable

短版

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

详细版本


声明

我知道这个主题是依赖于实现的,并且应该尝试手动执行此操作,因为编译器执行(正确)工作并且vtable不被视为标准(更不用说) dataformat)。 我在此作证,我已经阅读过几十本“不要这样做......只是,不要这样做!”并且我很清楚我的行为会带来无耻的后果。

因此(并且支持一个有条理的讨论)我将在Linux x64平台上使用g++ (4.x.x)作为我的参考。使用下面介绍的代码编译的任何软件都将使用相同的设置,因此就此而言,它应该是平台无关的。


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

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

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,但是我假设指向action2的地址等于索引1并且这部分让我感到困惑:如果有某种在定义action1的地址之前将偏移量添加到vtable中?

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

编译器知道每个虚拟方法指针的确切索引,因为,是的,编译器正在构建它们并用扩充代码替换虚拟方法的每个成员调用,这些代码与我上面使用的代码相同 - 但是知道要使用的索引。我曾经一次接受过有根据的猜测,即在我定义的虚拟方法之前没有偏移。

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

2 个答案:

答案 0 :(得分:1)

Linux使用的ABI是众所周知的:您应该查看virtual function table layout of the Itanium ABI上的部分。该文档还指定了查找vtable的对象布局。但是,我不知道你概述的方法是否有效。

请注意,尽管指向该文档,但我建议使用该信息来基于它来玩弄技巧!遗憾的是,您没有解释您的实际目标是什么,但似乎使用指向irtual函数成员的指针是一种更可靠的运行时调度到虚函数的方法:

double (IMethods::*method)(double)
    = flag? &IMethods:: action1: &INethods::actions2;
MethodProcessor mp;
double rc = (mp.*method)(3.14);

答案 1 :(得分:0)

我有几乎相同的问题,更糟糕的是,在我的情况下,这是针对采购代码...不要问我原因(只是告诉我:“不要这样做......只是,不要“或者至少使用像LLC这样的现有动态编译器......”。

当然,如果编译器设计者被要求遵循一些通用的C ++ ABI规范(或至少指定他们自己的规范),那么这种“折磨”可以被平滑。 显然,人们对遵守“通用C ++ ABI”(也常常被称为“ITANIUM C ++ ABI”,DietmarKühl提到)的共识越来越多。

因为你正在使用G ++,并且因为这是出于教育目的,我建议你看一下“-fdump-class-hierarchy”g ++开关做什么(你会发现vtable布局);这是我个人使用的,以确保我没有输错地址。


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

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

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