首先重新回顾一下关于类/对象大小的计算原则:
类大小计算遵循结构体对齐原则
第一个数据成员放在offset为0的位置
其它成员对齐至min(sizeof(member),#pragma pack(n)所指定的值)的整数倍。
整个结构体也要对齐,结构体总大小对齐至各个成员中最大对齐数的整数倍。
win32 可选的有1, 2, 4, 8, 16
linux 32 可选的有1, 2, 4
类的大小与数据成员有关与成员函数无关
类的大小与静态数据成员无关
虚继承对类的大小的影响
虚函数对类的大小的影响
下面通过实例来展示虚继承和虚函数对类大小造成的影响。
测试环境为:Win32 + Vs2008
一、只出现虚继承的情况
C++ Code
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 [
][
]<<endl;
cout<<p [
][
]<<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 [
][
]<<endl;
cout<<p [
][
]<<endl;
cout<<endl ;
cout<<p [
][
]<<endl;
cout<<p [
][
]<<endl;
BB* pp ;
pp = &dd ;
dd.bb_ =
;
//对象的内存模型在编译时就已经确定了,否则无法定义类的对象,因为要开辟内存
int base = pp-> bb_;
// 通过间接访问 (其实pp 已经偏移了20 ),这需要运行时的支持 cout<<
"dd.bb_=" <<base<< endl;
return
;
}
|
从输出的地址和虚基类表成员数据可以画出对象内存模型图:
virtual base table
本类地址与虚基类表指针地址的差
虚基类地址与虚基类表指针地址的差
virtual base table pointer(vbptr)
从程序可以看出pp是BB* 指针,pp首先指向dd内存,当执行pp->bb_时,先找到首个vbptr,找到虚基类BB地址与虚基类表指针地址的差,也即是20,接着pp偏移20个字节指向了dd对象中的BB部分,然后就访问到了bb_,这是在运行时才做的转换。
二、只出现虚函数的情况
C++ Code
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[
][
];
fun();
fun = (FUNC )p[
][
];
fun();
cout << endl ;
Derived d ;
p = (
int **)&d;
fun = (FUNC )p[
][
];
fun();
fun = (FUNC )p[
][
];
fun();
fun = (FUNC )p[
][
];
fun();
return
;
}
|
从输出的函数体可以画出对象内存模型图:
vtbl:虚函数表(存放虚函数的函数指针)
vptr:虚函数表指针
从输出可以看出,Derived类继承了Base::Fun1,而覆盖了Fun2,此外还有自己的Fun3。注意,因为Fun3是虚函数,才会出现在虚函数表,如果是一般函数是不会的,因为不用通过vptr间接访问。
三、虚继承与虚函数同时出现的情况:
C++ Code
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[
][
];
fun();
fun = (FUNC )p[
][
];
fun();
cout<<endl ;
B1 b1 ;
p = (
int **)&b1;
fun = (FUNC )p[
][
];
fun();
fun = (FUNC )p[
][
];
fun();
fun = (FUNC )p[
][
];
fun();
cout<<p [
][
]<<endl;
cout<<p [
][
]<<endl;
cout<<endl ;
DD dd ;
p = (
int **)ⅆ
fun = (FUNC )p[
][
];
fun();
fun = (FUNC )p[
][
];
// DD::vfdd 挂在 B1::vfb1的下面 fun();
fun = (FUNC )p[
][
];
fun();
fun = (FUNC )p[
][
];
fun();
fun = (FUNC )p[
][
];
fun();
cout<<p [
][
]<<endl;
cout<<p [
][
]<<endl;
cout<<p [
][
]<<endl;
cout<<p [
][
]<<endl;
return
;
}
|
从输出的虚基类表成员数据和虚函数体可以画出对象内存模型图:
注意:如果没有虚继承,则虚函数表会合并,一个类只会存在一个虚函数表和一个虚函数表指针(同个类的对象共享),当然也不会有
虚基类表和虚基类表指针的存在。
参考:
C++ primer 第四版
Effective C++ 3rd
C++编程规范