【问题标题】:pointer to access member function through virtual pointer通过虚拟指针访问成员函数的指针
【发布时间】:2015-12-17 01:44:34
【问题描述】:

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

2. 如何模拟它。你能提供编译器用来调用函数func1的实际语法吗(它如何替换ptr-&gt;func1())?

【问题讨论】:

  • 编译器在内部使用映射(哈希表或查找表或类似的东西)。关于模拟,我曾经写过一个小sn-p,对虚拟调用进行简化模拟,见github.com/vsoftco/snippets/blob/master/virtual_manual.cpp
  • @vsoftco:谢谢。我会复习的。顺便说一句,编译器使用的内部哈希表这个主题是否有任何参考?
  • 我不确定,可能有,但我不是这方面的专家。一本好书是amazon.com/Inside-Object-Model-Stanley-Lippman/dp/0201834545
  • @vsoftco 我不同意。 map (idea) 存在于编译器(在编译时)或解释器中。但是这种技术在运行时不存在于编译成“硬代码”语言、C++ 但 Java 和 C#(JVM 和 .NET)中。方法的位置是固定的,比如有const integer 实现
  • @vsoftco (limit of text) anurag86 问间接邻接运行时,你回答编译器,如果你们都互相理解就OK

标签: c++ virtual-functions vtable vptr


【解决方案1】:

不要将 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);
}

我希望这就是你要找的。​​p>

【讨论】:

  • 我同意在你写的条件下(数组在语言中从汇编到高级的定义略有不同,有些假设单词数组=表,有些则不是)。像带有函数指针的 C 结构这样的想象也可以,同意
  • @JacekCz 是的,这只是查看同一堆位的不同方式。
  • 两者都不一样。如果它是数组,那么我们将无法使用 -> 来访问元素,但是另一方面,结构/类使我们能够使用 vptr->bar。
  • @anurag86 我们在机器级别讨论。 CPU 不知道也不关心 C++ 允许您使用哪些运算符,返回类型或参数是什么等等。-&gt;[] 之间的区别在于 C++ 语法,它将两者转换为相同的地址模式汇编器。如果您愿意,我可以将分解更进一步,并将示例手动编译为程序集,以向您展示它们为什么相同,但我认为如果您不知道任何程序集,这将无济于事。
【解决方案2】:

背景:

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

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

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

它的唯一图像。实现必须正确触发覆盖classApionter-&gt;methodReimplementedInB()

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

4 很少有转换工具可以将“对象”文件(即从 MS 格式转换为 Borland 等),但通常/只有经典 C 是可能的/安全的,因为 vtable 的机器代码实现未知。

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

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

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?

【讨论】:

  • @J:是的,最后是机器码。但是即使名称不是必需的,编译器也必须实现一些机制来理解我应该调用什么函数,因此在调用解析期间的名称是必要的。为了理解它需要进行查找。当然,分辨率不会是 obj 的一部分,因为我们在这里讨论的是后期绑定。仍然存在关于如何解决呼叫的问题。幕后的逻辑是什么。
  • 好的。假设项目具有多个文件,类覆盖等文件是独立编译的(最新的 C++ IDE 将最后一次查看所有文件)。假设旧:func1() 的函数指针位于第二个位置并且编码二进制,func1() 被覆盖。部分解析由编译器完成,但最后一次解析(跳转到虚拟方法,方法通过地址而不是名称知道)在运行时
  • 我理解您的观点,即在您的示例中覆盖 0x126999 将被调用。但我的问题不同。作为实施者,我认为“将被称为”部分是理所当然的。问题更多的是关于 How? 。换句话说,试着想象你正在构建自己的编译器,你将如何处理调用成员函数(假设你有 vptr 和 vtable)的这种情况(不涉及多文件的复杂性)。你怎么理解 ptr->fun1 是调用 func1 而不是 func2。
  • fuct1() (和他的覆盖)在 vtables 中具有恒定的位置(即 2)并且不能被 func2() 误导(位置......说 5)。我不是英语母语,有问题如何清除。顺便说一句,当 func1 过载时(func1(int)func1(char *) 这些是此级别独立的两个函数)
  • 编译器构造函数构建一个映射(哈希表)map&lt;string,,int&gt; 与单元格"func1(void)" -> 2 "func2(void)" -> 5(在我的新示例中为"func1(char*)" -> 3 ) 并且生成的代码只包含 2,3 和 5。策略?命令是类声明?按字母顺序?没那么重要,但总是一样的
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-26
  • 2021-10-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多