通过虚指针访问成员函数的指针
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()
)?
背景:
编译后(没有调试信息)C/C++ 的二进制文件没有名字,运行时工作不需要名字,它只有机器代码
您可以将 vptr 视为经典 C 函数指针,在某种意义上,类型、参数列表等是已知的。
func1、func2等放在哪个位置并不重要,只要求顺序始终相同(因此多文件C++的所有部分必须以相同的方式编译,编译器设置等)。让我们想象一下,位置在声明顺序中,第一个父 class,然后在覆盖中新声明但是重新实现的虚拟位于较低的位置,就像来自父
这是唯一的图像。实现必须正确触发覆盖 classApionter->methodReimplementedInB()
- 通常是 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 的实现。
- 很难从高级代码接触 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);
}
希望这就是您要找的。
我看到一些文章,其中解释了 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()
)?
背景:
编译后(没有调试信息)C/C++ 的二进制文件没有名字,运行时工作不需要名字,它只有机器代码
您可以将 vptr 视为经典 C 函数指针,在某种意义上,类型、参数列表等是已知的。
func1、func2等放在哪个位置并不重要,只要求顺序始终相同(因此多文件C++的所有部分必须以相同的方式编译,编译器设置等)。让我们想象一下,位置在声明顺序中,第一个父 class,然后在覆盖中新声明但是重新实现的虚拟位于较低的位置,就像来自父
这是唯一的图像。实现必须正确触发覆盖 classApionter->methodReimplementedInB()
- 通常是 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 的实现。
- 很难从高级代码接触 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);
}
希望这就是您要找的。