1.虚函数表指针位置
- 一个类有虚函数,则这个类就会有一个虚函数表
- 该类对象里会有一个虚函数表指针,指向虚函数表的起始地址
位置:虚函数表指针的位置取决于编译器,可能在对象内存开头,也可能在末尾,需要经过测试。
测试代码:
#include <iostream>
class A
{
public:
int i;
virtual void testfunc() {}
};
int main()
{
A a;
int ilen = sizeof(a);
char* p1 = reinterpret_cast<char*>(&a);
char* p2 = reinterpret_cast<char*>(&(a.i));
if (p1 = p2)
{
std::cout << "虚函数表指针在对象内存末尾";
}
else
{
std::cout << "虚函数表指针在对象内存开头";
}
}
2.手动调用虚函数
class Base
{
public:
virtual void a() { std::cout << "BASE::a()"; }
virtual void b() { std::cout << "BASE::b()"; }
virtual void c() { std::cout << "BASE::c()"; }
};
class Derive : public Base
{
public:
virtual void a() { std::cout << "Derive::a()"; }
};
int main()
{
Derive* pd = new Derive(); // 派生类指针
int* ptr = (int*)pd; //指向对象pd的指针转换为int*,pd对象里只有虚函数表指针
unsigned int * vptr = (unsigned int*)(*ptr); //(*ptr)表示指向的对象,也就是pd对象,内存里的值就是虚函数表的地址。
for (int i = 0; i <= 2; i++)
{
printf("vptr[%d] = 0x: %p\n", i, &vptr[i]);
}
}
3.虚函数表分析
- 包含虚函数的类才有虚函数表,同属于一个类的 对象共享这个虚函数表。
- 父类中有虚函数等于子类有虚函数。父类中有虚函数表,则子类肯定也有虚函数表,即使不覆盖父类的任何虚函数。
- 如果子类中完全没有新的虚函数,则父类和子类的虚函数表内容一致,仅仅是内容一致。两个虚函数表位于不同的内存地址
4.子类复制给父类,虚函数表指针处理
Base base = derive;
按道理,base对象的数据是从derive复制过来,即两个虚函数表指针应该一致,但实际不一致。
编译器所做的处理:1.生成一个base对象 2.用derive初始化base对象的值,初始化时,
5.多重继承下的虚函数表
- 一个对象,如果它所属的类有多个基类,则有多个虚函数表指针
- 多重继承下,对应各个基类的虚函数表指针按继承顺序放置在类的内存空间中,且子类与第一个基类共用一个vptr虚函数表指针
6.虚函数表创建时机
虚函数表指针创建时机
vptr是跟着对象走的,对象创建出来,才存在这个vptr。
- 编译时候,编译器会往类的构造函数中安插为vptr赋值的语句
- 运行时候,创建对象时,执行构造函数,因为有为vptr赋值的语句,所以vptr变得有效
虚函数表创建时机
- 编译时候,编译器已经为每个类确定好了对应的虚函数表的内容
7.静态联编和动态联编
静态联编:编译时候就能确定调用哪个函数,把调用语句和被调用函数绑定到一起,通过call调用函数
动态联编:运行时候,根据实际情况,动态把调用函数和被调用函数绑定到一起,动态联编一般只有在多态和虚函数情况下才存在
直接定义对象:
通过对象调用虚函数,根本用不到虚函数表,即便是把虚函数表指针清零,也不影响调用,因为是通过静态联编在编译时候确定了函数调用,通过call调用
定义指针:
通过指针调用虚函数,并不是直接调用,而是通过虚函数表指针找到虚函数表,然后找到虚函数。