C++多态底层原理:虚函数表
虚函数表
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:
int a;
virtual void f1(){ cout << "A f1\n"; }
virtual void f2(){ cout << "A f2\n"; }
};
class B {
public:
int b;
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;
// 因为A虚函数表后边还有一个int a,vptr+1是int a的地址
// 所以vptr+2才是B的虚函数表地址(64位系统内存对齐)
cout << "B虚函数表地址:" << (void*)vptr[2] << endl;
((FUN)vptr[0][0])();
((FUN)vptr[0][1])();
((FUN)vptr[2][0])();
((FUN)vptr[2][1])();
((FUN)vptr[2][2])();
/*输出:
A虚函数表地址:0x104bdc110
B虚函数表地址:0x104bdc130
A f1
A f2
B f1
B f2
B f3
*/
}
int main()
{
C c;
f(c);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话