1,类大小计算遵循结构体对齐原则
什么是内存对齐
编译器为每个“数据单元”按排在某个合适的位置上。
C、C++语言非常灵活,它允许你干涉“内存对齐”
为什么要对齐
性能原因:在对齐的地址上访问数据快。
如何对齐
第一个数据成员放在offset为0的位置
其它成员对齐至min(sizeof(member),#pragma pack(n)所指定的值)的整数倍。
整个结构体也要对齐,结构体总大小对齐至各个成员中最大对齐数的整数倍。
举个例子,
struct test
{char a;
double b;
char c;
};
根据规则1,a 在位置0;根据规则2,因为VC默认pack为8,可以通过项目-》属性-》c/c++ -》代码生成-》结构体成员对齐选项修改,也可以使用#pragma pack(n) 来修改,#pragma pack() 取消修改,那么b 占据8~15;根据规则2,c在16;现在总共17个字节,根据规则3,结构体总大小需对齐到8的整数倍,即总共是24个字节。
如果将pack 修改为4,则总大小为16。在VC上pack 共有1,2,4,8,16 等5种选择,而linux g++ 则只有1,2,4 可选,默认是4。
2,类的大小与数据成员有关与成员函数无关(空类大小为1个字节),类的大小与静态数据成员无关。
3,虚函数、虚继承对类的大小的影响
第一个数据成员放在offset为0的位置
其它成员对齐至min(sizeof(member),#pragma pack(n)所指定的值)的整数倍。
整个结构体也要对齐,结构体总大小对齐至各个成员中最大对齐数的整数倍。
类的大小与静态数据成员无关
虚继承对类的大小的影响
虚函数对类的大小的影响
下面通过实例来展示虚继承和虚函数对类大小造成的影响。
测试环境为:Win32 + Vs2008
一、只出现虚继承的情况
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
#include <iostream>
using namespace std; class BB { public : int bb_ ; }; class B1 : virtual public BB { public : int b1_ ; }; class B2 : virtual public BB { public : int b2_ ; }; class DD : public B1, public B2 { public : int dd_ ; }; int main ( void) { cout<< sizeof (BB)<< endl; cout<< sizeof (B1)<< endl; cout<< sizeof (DD)<< endl; B1 b1 ; int** p ; cout<<&b1 <<endl; cout<<&b1 .bb_<< endl; cout<<&b1 .b1_<< endl; p = ( int **)&b1; cout<<p [ 0][ 0]<<endl; cout<<p [ 0][ 1]<<endl; DD dd ; cout<<&dd <<endl; cout<<&dd .bb_<< endl; cout<<&dd .b1_<< endl; cout<<&dd .b2_<< endl; cout<<&dd .dd_<< endl; p = ( int **)ⅆ cout<<p [ 0][ 0]<<endl; cout<<p [ 0][ 1]<<endl; cout<<endl ; cout<<p [ 2][ 0]<<endl; cout<<p [ 2][ 1]<<endl; BB* pp ; pp = &dd ; dd.bb_ = 10; //对象的内存模型在编译时就已经确定了,否则无法定义类的对象,因为要开辟内存 int base = pp-> bb_; // 通过间接访问 (其实pp 已经偏移了20 ),这需要运行时的支持 cout<< "dd.bb_=" <<base<< endl; return 0; } |
从输出的地址和虚基类表成员数据可以画出对象内存模型图:
virtual base table
本类地址与虚基类表指针地址的差
虚基类地址与虚基类表指针地址的差
virtual base table pointer(vbptr)
从程序可以看出pp是BB* 指针,pp首先指向dd内存,当执行pp->bb_时,先找到首个vbptr,找到虚基类BB地址与虚基类表指针地址的差,也即是20,接着pp偏移20个字节指向了dd对象中的BB部分,然后就访问到了bb_,这是在运行时才做的转换。
二、只出现虚函数的情况
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
#include <iostream>
using namespace std; class Base { public : virtual void Fun1() { cout << "Base::Fun1 ..." << endl; } virtual void Fun2() { cout << "Base::Fun2 ..." << endl; } int data1_ ; }; class Derived : public Base { public : void Fun2 () { cout << "Derived::Fun2 ..." << endl; } virtual void Fun3() { cout << "Derived::Fun3 ..." << endl; } int data2_ ; }; typedef void (* FUNC)( void ); int main ( void) { cout << sizeof (Base) << endl; cout << sizeof (Derived) << endl; Base b ; int **p = ( int **)& b; FUNC fun = (FUNC) p[ 0][ 0]; fun(); fun = (FUNC )p[ 0][ 1]; fun(); cout << endl ; Derived d ; p = ( int **)&d; fun = (FUNC )p[ 0][ 0]; fun(); fun = (FUNC )p[ 0][ 1]; fun(); fun = (FUNC )p[ 0][ 2]; fun(); return 0; } |
从输出的函数体可以画出对象内存模型图:
vtbl:虚函数表(存放虚函数的函数指针)
vptr:虚函数表指针
从输出可以看出,Derived类继承了Base::Fun1,而覆盖了Fun2,此外还有自己的Fun3。注意,因为Fun3是虚函数,才会出现在虚函数表,如果是一般函数是不会的,因为不用通过vptr间接访问。
三、虚继承与虚函数同时出现的情况:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
#include <iostream>
using namespace std; class BB { public : virtual void vfbb() { cout<< "BB::vfbb" <<endl; } virtual void vfbb2() { cout<< "BB::vfbb2" <<endl; } int bb_ ; }; class B1 : virtual public BB { public : virtual void vfb1() { cout<< "B1::vfb1" <<endl; } int b1_ ; }; class B2 : virtual public BB { public : virtual void vfb2() { cout<< "B2::vfb2" <<endl; } int b2_ ; }; class DD : public B1, public B2 { public : virtual void vfdd() { cout<< "DD::vfdd" <<endl; } int dd_ ; }; typedef void (* FUNC)( void); int main ( void) { cout<< sizeof (BB)<< endl; cout<< sizeof (B1)<< endl; cout<< sizeof (DD)<< endl; BB bb ; int** p ; p = ( int **)&bb; FUNC fun ; fun = (FUNC )p[ 0][ 0]; fun(); fun = (FUNC )p[ 0][ 1]; fun(); cout<<endl ; B1 b1 ; p = ( int **)&b1; fun = (FUNC )p[ 0][ 0]; fun(); fun = (FUNC )p[ 3][ 0]; fun(); fun = (FUNC )p[ 3][ 1]; fun(); cout<<p [ 1][ 0]<<endl; cout<<p [ 1][ 1]<<endl; cout<<endl ; DD dd ; p = ( int **)ⅆ fun = (FUNC )p[ 0][ 0]; fun(); fun = (FUNC )p[ 0][ 1]; // DD::vfdd 挂在 B1::vfb1的下面 fun(); fun = (FUNC )p[ 3][ 0]; fun(); fun = (FUNC )p[ 7][ 0]; fun(); fun = (FUNC )p[ 7][ 1]; fun(); cout<<p [ 1][ 0]<<endl; cout<<p [ 1][ 1]<<endl; cout<<p [ 4][ 0]<<endl; cout<<p [ 4][ 1]<<endl; return 0; } |
从输出的虚基类表成员数据和虚函数体可以画出对象内存模型图: