C++多态底层原理:虚函数表

发布时间 2024-01-04 19:22:28作者: hellozhangjz

虚函数表

C++ 对象模型

在有虚函数的情况下,C++对象的模型可以概括为:虚函数表指针+数据struct。在对象所在的内存里:前8个字节(64位系统)是虚函数表所在地址,后边是对象中的member data。在多态的实现里,父指针就是根据所指向内存里的第一个地址来找到对应的虚函数表从而实现多态。

可以用以下程序验证C++内存模型:

void show_bits(void *ptr, int len)
{
	unsigned char *p = (unsigned char*) ptr;
	for(int i = len-1; i >= 0; --i)
		printf("%x", p[i]);
	printf("\n");
}

class A{
public:
	virtual void f1(){}
	virtual void f2(){}
};

class B:public A {
public:
	int a = -1;
	void f1(){ cout << "B f1\n"; }
	void f2(){ cout << "B f2\n"; }
};
using FUN = void(*)();

int main()
{
	B b;
	// 虚函数表数组的地址8个字节,加上int a的4个字节
	show_bits(&b, 12); // 输出:ffffffff000128c18
	// b.a 的4个字节
	show_bits(&b.a, 4); // 输出:ffffffff,0x000128c18指向的就是虚函数表
						
	cout << sizeof(b) << endl; // 输出16,因为是64位系统,所以12B会内存对齐到16B

	//得到虚函数表数组地址
	void **vtbl = *(void***)&b;

	// 虚函数表数组第一个元素就是第一个虚函数f1的地址
	((FUN)(vtbl[0]))(); //输出:B f1
	// 虚函数表数组第一个元素就是第一个虚函数f2的地址
	((FUN)(vtbl[1]))(); //输出:B f2
  
  B c;
	show_bits(&c, 12); // 输出:ffffffff000128c18,说明同一个类的不同对象指向同一个虚函数表
    return 0;
}

以下程序验证多态:

class A{
public:
	virtual void f1(){ cout << "A f1\n"; }
	virtual void f2(){ cout << "A f2\n"; }
};

class B:public A {
public:
	void f1(){ cout << "B f1\n"; }
	void f2(){ cout << "B f2\n"; }
};

using FUN = void(*)();
// 多态
void f(A& a) {
	void **vtbl = *(void***)&a;
	cout << "虚函数表地址:" << (void*)vtbl << endl;
	((FUN)(vtbl[0]))();
}

int main()
{
	A a1, a2;
	B b1, b2;
	f(a1);
	f(a2);
	f(b1);
	f(b2);
    return 0;
}
/* 输出:
虚函数表地址:0x100a14108
A f1
虚函数表地址:0x100a14108
A f1
虚函数表地址:0x100a14138
B f1
虚函数表地址:0x100a14138
B f1
*/

多重继承

与单继承的C++对象模型类似,只不过从几个类继承,就有几个虚函数表:

以下程序验证:

class A{
public:
	virtual void f1(){ cout << "A f1\n"; }
	virtual void f2(){ cout << "A f2\n"; }
};

class B {
public:
	virtual void f1(){ cout << "B f1\n"; }
	virtual void f2(){ cout << "B f2\n"; }
	virtual void f3() {cout << "B f3\n"; }
};

class C: public A, public B {
public:
	// void f1() { cout << "C f1\n"; }
	// void f2() { cout << "C f2\n"; }
	// void f3() { cout << "C f3\n"; }
};
using FUN = void(*)();

void f(A& a) {
	void ***vptr = (void***)&a;
	cout << "A虚函数表地址:" << (void*)vptr[0] << endl;
	cout << "B虚函数表地址:" << (void*)vptr[1] << endl;
	((FUN)vptr[0][0])();
	((FUN)vptr[0][1])();
	((FUN)vptr[1][0])();
	((FUN)vptr[1][1])();
	((FUN)vptr[1][2])();
  /*输出:
    A虚函数表地址:0x104bdc110
    B虚函数表地址:0x104bdc130
    A f1
    A f2
    B f1
    B f2
    B f3
  */
}

int main()
{
	C c;
	f(c);
    return 0;
}