通过虚指针访问成员函数的指针

pointer to access member function through virtual pointer

我看到一些文章,其中解释了 vptr 和 vtable。 我知道在 class 存储虚函数的情况下,对象中的第一个指针是 vtable 的 vptr,vtable 的数组条目是指向函数的指针,其顺序与它们在 class 中出现的顺序相同(我已经用我的测试程序验证过)。 但我试图了解编译器必须输入什么语法才能调用适当的函数。

示例:

class Base
 {
   virtual void func1() 
   { 
       cout << "Called me" << endl; 
   }
};
int main()
{
  Base obj;
  Base *ptr;
  ptr=&obj;

// void* is not needed. func1 can be accessed directly with obj or ptr using vptr/vtable
  void* ptrVoid=ptr; 

// I can call the first virtual function in the following way:
  void (*firstfunc)()=(void (*)(void))(*(int*)*(int*)ptrVoid); 
  firstfunc();
}

问题:

1. 但我真正想了解的是编译器如何用 vptr 替换对 ptr->func1() 的调用? 如果我要模拟通话那么我应该怎么做?我应该重载 -> 运算符吗?但即使这样也无济于事,因为我不知道 func1 这个名字到底是什么。即使他们说编译器通过 vptr 访问 vtable,它仍然如何知道 func1 的条目是第一个数组并且 func2 的条目是数组中的第二个元素?必须有一些函数名称到数组元素的映射。

2. 怎么模拟呢。您能否提供编译器用于调用函数 func1 的实际语法(它如何替换 ptr->func1())?

背景:

  1. 编译后(没有调试信息)C/C++ 的二进制文件没有名字,运行时工作不需要名字,它只有机器代码

  2. 您可以将 vptr 视为经典 C 函数指针,在某种意义上,类型、参数列表等是已知的。

  3. func1、func2等放在哪个位置并不重要,只要求顺序始终相同(因此多文件C++的所有部分必须以相同的方式编译,编译器设置等)。让我们想象一下,位置在声明顺序中,第一个父 class,然后在覆盖中新声明但是重新实现的虚拟位于较低的位置,就像来自父

这是唯一的图像。实现必须正确触发覆盖 classApionter->methodReimplementedInB()

  1. 通常是 C++ 编译器 has/had(我的知识来自 16/32b 年的迁移)2-4 选项可以针对 speed/size 等优化 vtables。经典 C sizeof() 非常适合理解(size of data plus ev.alignment),在C++中sizeof更大,但是可以保证是2,4,8字节。

4 很少有转换工具可以将 "object" 文件从 MS 格式转换为 Borland 等,但是 usually/only classic C 是 possible/safe,因为未知的机器代码vtable 的实现。

  1. 很难从高级代码接触 vtable,为中间文件(.obj、.etc)触发分析器

编辑:关于运行时的故事不同于关于编译的故事。我的回答是关于编译代码和运行时

EDIT2:准汇编代码(来自我的头脑)

load ax, 2
call vt[ax]

vt:
0x123456
0x126785  // virlual parent func1()

推导:

vt:
0x123456
0x126999 // overriden finc1()
0x456788 // new method

EDIT3:顺便说一句,我不能完全同意 C++ 总是比 JVM/.NET 速度更快,因为 "these are interpreted"。 C++ 有 "intepretation" 的一部分,而解释的部分是 groving:真正的 component/GUI 框架也有解释之间的联系(例如 map)。在我们的讨论之外:使用 C++ 删除或 GC,哪种内存模型更好?

不要将 vtable 视为数组。它只是一个数组,如果你去掉它的所有 C++ 知道的关于它的东西,除了它的成员的大小。相反,将其视为第二个 struct,其成员都是指向函数的指针。

假设我有一个像这样的 class:

struct Foo {
    virtual void bar();
    virtual int baz(int qux);
    int quz;
}

int callSomeFun(Foo* foo) {
    foo->bar();
    return foo->baz(2);
}

分解 1 个步骤:

class Foo;
// adding Foo* parameter to simulate the this pointer, which
// in the above would be a pointer to foo.
struct FooVtable {
    void (*bar)(Foo* foo);
    int (*baz)(Foo* foo, int qux);
}
struct Foo {
    FooVtable* vptr;
    int quz;
}

int callSomeFun(Foo* foo) {
    foo->vptr->bar(foo);
    return foo->vptr->baz(foo, 2);
}

希望这就是您要找的。