C++ 中 vtable 的函数解析
Function resolution from vtable in C++
在阅读了更多有关名称修改的信息后,我对 vtable 感到困惑。
例如:
class Base
{
public:
virtual void print()
{
}
};
class A : public Base
{
public:
void hello()
{
....
}
void print()
{
}
};
A obj;
obj.hello();
Base* test = new A();
test->print();
根据我的理解,名称管理 obj.hello()
调用现在将转换为类似 _ZASDhellov(&obj)
的内容
- 如何从 vtable 调用此虚函数?
- 我的疯狂猜测
test->__vtable[_ZASDprintv](&test(dynamic cast to derived???))
是正确的吗?
- 如何从 vtable 解析函数名称?
首先,vtables 在任何方面都不是 C++ 语言的一部分,而是特定编译器使用的实现细节。下面我描述一种常用的方式。
其次,您的函数 hello
不是虚函数。要使其成为虚拟的,您只需在声明前添加 virtual
。
假设它现在是虚拟的:您的猜测非常接近。事实上,vtable
(一个指针存储在虚拟 class 的每个实例中)是一个函数指针数组。在其中查找特定函数的方式是按其序号。 A
中第一个声明的虚函数是其 vtable
中的第一个条目,第二个是第二个条目,依此类推。如果 A
有一个基础 class,A
在 table 中的第一个(非覆盖)虚函数的索引将是 n+1
,其中 n
是其基class的最后一个虚函数的索引。如果 A
有多个基数 class,则它们的条目按照它们声明为 A
的基数 class 的顺序排在 A
的条目之前。
如果A
使用虚继承,图就复杂一点,除非你特别感兴趣我就不细说了。
更新: 我将根据要求为虚拟继承案例添加一个非常简短的描述。如果 A
有 Base
作为虚拟基础 class,A
的 vtable
将在最开始(在函数地址之前)存储字节偏移量Base
的数据从 A
对象开始。这是必要的,因为与正常继承不同,基 class 的数据不在派生 class 的数据之前 - 而是在派生 class 的数据之后。因此实际上,任何对 Base
中定义的虚函数的函数调用都必须使其 this
指针偏移该量。此外,Base
必须有自己的 vtable
指针,就在它希望找到它的数据的开头。因此完整的 A
对象将包含两个 vtable
指针而不是一个。第二个指针指向的实际 vtable
将与第一个 vtable
指针相同,除了提前跳过上述偏移条目(以便任何 Base
代码使用 vtable
会在预期的开头找到第一个虚函数)。除了这些差异之外,vtable
本身与以前相同。
在阅读了更多有关名称修改的信息后,我对 vtable 感到困惑。 例如:
class Base
{
public:
virtual void print()
{
}
};
class A : public Base
{
public:
void hello()
{
....
}
void print()
{
}
};
A obj;
obj.hello();
Base* test = new A();
test->print();
根据我的理解,名称管理 obj.hello()
调用现在将转换为类似 _ZASDhellov(&obj)
的内容
- 如何从 vtable 调用此虚函数?
- 我的疯狂猜测
test->__vtable[_ZASDprintv](&test(dynamic cast to derived???))
是正确的吗? - 如何从 vtable 解析函数名称?
首先,vtables 在任何方面都不是 C++ 语言的一部分,而是特定编译器使用的实现细节。下面我描述一种常用的方式。
其次,您的函数 hello
不是虚函数。要使其成为虚拟的,您只需在声明前添加 virtual
。
假设它现在是虚拟的:您的猜测非常接近。事实上,vtable
(一个指针存储在虚拟 class 的每个实例中)是一个函数指针数组。在其中查找特定函数的方式是按其序号。 A
中第一个声明的虚函数是其 vtable
中的第一个条目,第二个是第二个条目,依此类推。如果 A
有一个基础 class,A
在 table 中的第一个(非覆盖)虚函数的索引将是 n+1
,其中 n
是其基class的最后一个虚函数的索引。如果 A
有多个基数 class,则它们的条目按照它们声明为 A
的基数 class 的顺序排在 A
的条目之前。
如果A
使用虚继承,图就复杂一点,除非你特别感兴趣我就不细说了。
更新: 我将根据要求为虚拟继承案例添加一个非常简短的描述。如果 A
有 Base
作为虚拟基础 class,A
的 vtable
将在最开始(在函数地址之前)存储字节偏移量Base
的数据从 A
对象开始。这是必要的,因为与正常继承不同,基 class 的数据不在派生 class 的数据之前 - 而是在派生 class 的数据之后。因此实际上,任何对 Base
中定义的虚函数的函数调用都必须使其 this
指针偏移该量。此外,Base
必须有自己的 vtable
指针,就在它希望找到它的数据的开头。因此完整的 A
对象将包含两个 vtable
指针而不是一个。第二个指针指向的实际 vtable
将与第一个 vtable
指针相同,除了提前跳过上述偏移条目(以便任何 Base
代码使用 vtable
会在预期的开头找到第一个虚函数)。除了这些差异之外,vtable
本身与以前相同。