首先需要明确的是,对象模型不属于C++标准的一部分,如何实现由编译器厂商决定,不过目前主流厂商的编译器实现的都类似,有一些场景使用了经过裁剪的C++,和这儿谈论的对象模型可能完全不同。编译器的内存对齐对于谈论对象模型来说没有帮助,因此禁用。不考虑虚继承,多继承,多级继承的情况。
测试平台为VS2015,GCC4.8.4
//对于VS,使用#pragma pack(1)禁用编译器对齐
//对于GCC,使用__attribute__((__packed__))禁用编译器对齐
class Fruit
{
int no;
double weight;
char key;
public:
void print() { }
virtual void process(){ }
}__attribute__((__packed__);
class Apple: public Fruit{
int size;
char type;
public:
void save() { }
virtual void process(){ }
}__attribute__((__packed__));
int main()
{
Fruit f;
Apple a;
cout << sizeof(f) << endl;
cout << sizeof(a) <<endl;
return 0;
}
x86(-m32)模式下:
-
VS的结果
- sizeof(f) = 17
- sizeof(a) = 22
-
内存图
- int + char + double = 13,再加上一个32位的虚指针,正好17
-
GCC
- sizeof(f) = 17
- sizeof(a) = 22
-
内存图
- 和VS的结果是一样的
内存符号的名字暗示了函数继承的时候继承的只是所有权,函数体只有一份(Fruit::process, Apple::process)
另一件事就是虚表显然是被类所有的,也可以说是static的,不会占用实例的内存空间
x64(-m64)模式下:
-
VS的结果
- sizeof(f) = 21
- sizeof(a) = 26
-
GCC
- sizeof(f) = 21
- sizeof(a) = 26
算一下,差异正好是一个32位指针,所以布局一致,只是指针大了一倍
vtable倒是说明了一件事,C++的动态绑定(Dynamic binding/Late Binding)和大家想要的还不完全是一个东西,本质上还是静态绑定,毕竟编译期已经把一切偏移量都写死了,只是具有了多态特性,基类指针指向不同派生类实例的时候可以通过虚函数表跳到不同的函数入口点。和大家想要的拿着名字去找函数的运行时绑定还是有很大区别的