3

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

例子:

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

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

4

2 回答 2

2

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

假设我有这样的课程:

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);
}

我希望这就是你要找的。

于 2015-09-20T10:54:15.560 回答
0

背景:

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

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

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

它唯一的形象。实现必须正确触发覆盖classApionter->methodReimplementedInB()

  1. 通常 C++ 编译器有/有(我的知识来自 16/32b 年的迁移)2-4 个选项来针对速度/大小等优化 vtables。经典 Csizeof()很好理解(数据大小加上 ev。对齐),在 C++ sizeof较大,但可以保证是2,4,8字节。

4 很少有转换工具可以将“对象”文件(即从 MS 格式转换为 Borland 等),但通常/只有经典 C 是可能的/安全的,因为 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 速度,因为“这些都是解释的”。C++ 具有“解释”的一部分,而解释的部分正在发展:真正的组件/GUI 框架之间也有解释的连接(例如地图)。在我们的讨论之外:哪种内存模型更好,使用 C++ 删除还是 GC?

于 2015-09-20T07:53:36.520 回答