多态类在编译期间会生成虚函数表存放在只读存储区,虚函数表类似于虚函数的数组,函数的代码存储在代码区。在运行时,多态类的对象在内存开始位置存放着虚表指针,对象访问虚函数时,通过虚表指针找到虚表进而找到虚函数地址。
多态类在继承时,子类对象会将父类的徐标志镇和所有变量都保存到自己的内存里(即使父类private变量的内存也会继承下来,只会无法访问到),也就是子类对象内存中存储着一个父类对象。如果使菱形继承,那么"菱形"底部的类就会存储两份"菱形"顶端的类对象内存,通过虚继承可优化成一份,暂且不表。
接下来,我们通过代码验证多态类的内存分布,以及分析多态类对象的大小。
#include <iostream>
class Base {
public:
virtual void f() { std::cout << "Base::f\n"; }
private:
int v;
};
class Derive : public Base {
public:
virtual void f() { std::cout << "Derive::f\n"; }
virtual void foo() { std::cout << "Derive::foo\n"; }
char ch;
int v1;
};
typedef void(*Fn)();
// 64 bit system
int main() {
std::cout << sizeof(Base) << ", " << sizeof(Derive) << std::endl; // 16, 24
Base* b = new Derive;
// 为了对对象成员赋值
Derive* real = dynamic_cast<Derive>(ch);
real->ch= 1;
real->v1 = 13;
// 虚函数地址访问
Fn f = (Fn)(*(int64_t*)(*(int64_t*)b)); // 第一次*(int64_t*)b 取得虚表指针的值,也就是虚函数数组的首地址,第二次是取得第一个虚函数地址
Fn f1 = (Fn)(*((int64_t*)(*(int64_t*)b) + 1)); // 第一次*(int64_t*)b 取得虚表指针的值,也就是虚函数数组的首地址,第二次是取得第2个虚函数地址
f();
f1();
std::cout << "b->ch:" << *(char*)((int8_t*)b + 12) << "\n";
std::cout << "b->v1:" << *(int*)((int8_t*)b + 16) << "\n";
/**
Derive::f
Derive::foo
b->ch: //美打出来是因为类型是字符,按照字符转玛了
b->v1:13
**/
return 0;
}
子类对象内存中一次存放了:虚表指针-父类成员变量-子类成员变量
首先计算下大小:64为系统中指针占8B,于是按字节对其 sizeof(Base) =8 + 4 = 12, 最后对最大的成员类型取整,16;
sizoef(Dervice)=8(子类指针) + 4 + 1 + (3+4) (int v1需要对齐)=20, 最后对指针类型取整,24;
上面按地址打印验证的多态类对象内存分布。