从零开始学C++之虚继承和虚函数对C++对象内存模型造成的影响(类/对象的大小)
2016-10-26 08:23
896 查看
首先重新回顾一下关于类/对象大小的计算原则:
类大小计算遵循结构体对齐原则
win32 可选的有1, 2, 4, 8, 16
linux 32 可选的有1, 2, 4
类的大小与数据成员有关与成员函数无关
类的大小与静态数据成员无关
虚继承对类的大小的影响
虚函数对类的大小的影响
下面通过实例来展示虚继承和虚函数对类大小造成的影响。
测试环境为:Win32 + Vs2008
一、只出现虚继承的情况
从输出的地址和虚基类表成员数据可以画出对象内存模型图:
本类地址与虚基类表指针地址的差
虚基类地址与虚基类表指针地址的差
从程序可以看出pp是BB* 指针,通过打印pp 的值与&dd 比较可知,
pp实际上已经偏移了20个字节,如何实现的呢?先找到首个vbptr,找到虚基类BB地址与虚基类表指针地址的差,也即是20,接着pp偏移20个字节指向了dd对象中的BB部分,然后就访问到了bb_,这是在运行时才做的转换。记住:C++标准规定对对象取地址将始终为对应类型的首地址。
二、只出现虚函数的情况
(一):一般继承
从输出的函数体可以画出对象内存模型图:
vtbl:虚函数表(存放虚函数的函数指针)
vptr:虚函数表指针
从输出可以看出,Derived类继承了Base::Fun1,而覆盖了Fun2,此外还有自己的Fun3。注意,因为Fun3是虚函数,才会出现在虚函数表,如果是一般函数是不会的,因为不用通过vptr间接访问。
(二)、钻石继承
从成员输出的地址和通过虚函数表指针访问到的函数可以画出模型:
DD::vfdd 的位置跟继承的顺序有关,如果DD先继承的是B2, 那么它将跟在B2::vfb2 的下面。
如果派生类是从多个基类继承或者有多个继承分支(从所有根类开始算起),而其中若干个继承分支上出现了多态类,则派生类将从这些分支中的每个分支上继承一个vptr,编译器也将为它生成多个vtable,有几个vptr就生成几个vtable(每个vptr分别指向其中一个),分别与它的多态基类对应。
三、虚继承与虚函数同时出现的情况:
从输出的虚基类表成员数据和虚函数体可以画出对象内存模型图:
上图中vfdd 出现的位置跟继承的顺序有关,如果DD先继承的是B2,那么它将跟在vfb2 的下面。
注意:如果没有虚继承,则虚函数表会合并,一个类只会存在一个虚函数表和一个虚函数表指针(同个类的对象共享),当然也不会有虚基类表和虚基类表指针的存在。
但如果是钻石继承,那么是会存在两份虚函数表和两份虚函数表指针的。
参考:
《深入探索C++对象模型》
C++ primer 第四版
Effective C++ 3rd
C++编程规范
转载自http://blog.csdn.net/jnu_simba/article/details/9319019
类大小计算遵循结构体对齐原则
第一个数据成员放在offset为0的位置 其它成员对齐至min(sizeof(member),#pragma pack(n)所指定的值)的整数倍。 整个结构体也要对齐,结构体总大小对齐至各个min中最大值的整数倍。
win32 可选的有1, 2, 4, 8, 16
linux 32 可选的有1, 2, 4
类的大小与数据成员有关与成员函数无关
类的大小与静态数据成员无关
虚继承对类的大小的影响
虚函数对类的大小的影响
下面通过实例来展示虚继承和虚函数对类大小造成的影响。
测试环境为:Win32 + Vs2008
一、只出现虚继承的情况
#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 tablepointer(vbptr)
从程序可以看出pp是BB* 指针,通过打印pp 的值与&dd 比较可知,
cout<<(void*)&dd<<endl; cout<<(void*)pp<<endl;
pp实际上已经偏移了20个字节,如何实现的呢?先找到首个vbptr,找到虚基类BB地址与虚基类表指针地址的差,也即是20,接着pp偏移20个字节指向了dd对象中的BB部分,然后就访问到了bb_,这是在运行时才做的转换。记住:C++标准规定对对象取地址将始终为对应类型的首地址。
二、只出现虚函数的情况
(一):一般继承
#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间接访问。
(二)、钻石继承
#include <iostream> using namespace std; class BB { public: virtual void vpbb() { cout << "BB:vpbb().." << endl; } int bb_; }; class B1 : public BB { public: virtual void vpb1() { cout << "B1:vpb1().." << endl; } int b1_; }; class B2 : public BB { public: virtual void vpb2() { cout << "B2:vpb2().." << endl; } int b2_; }; class DD : public B1, public B2 { public: virtual void vpdd() { cout << "DD:vpdd().." << endl; } int dd_; }; typedef void (* FUNC)(void ); int main() { cout << sizeof(BB) << endl; cout << sizeof(B1) << endl; cout << sizeof(DD) << endl; cout << endl; DD dd ; cout << &dd << endl; cout << &dd.B1::bb_ << endl; cout << &dd.B2::bb_ << endl; cout << &dd .b1_ << endl; cout << &dd .b2_ << endl; cout << &dd .dd_ << endl; cout << endl; B1 b ; int **p = (int **)& b; FUNC fun = (FUNC) p[0][0]; fun(); fun = (FUNC )p[0][1]; fun(); cout << endl ; p = (int **)&dd fun = (FUNC)p[0][0]; fun(); fun = (FUNC)p[0][1]; fun(); fun = (FUNC)p[0][2]; fun(); fun = (FUNC)p[3][0]; fun(); fun = (FUNC)p[3][1]; fun(); cout << endl; return 0; }
从成员输出的地址和通过虚函数表指针访问到的函数可以画出模型:
DD::vfdd 的位置跟继承的顺序有关,如果DD先继承的是B2, 那么它将跟在B2::vfb2 的下面。
如果派生类是从多个基类继承或者有多个继承分支(从所有根类开始算起),而其中若干个继承分支上出现了多态类,则派生类将从这些分支中的每个分支上继承一个vptr,编译器也将为它生成多个vtable,有几个vptr就生成几个vtable(每个vptr分别指向其中一个),分别与它的多态基类对应。
三、虚继承与虚函数同时出现的情况:
#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; }
从输出的虚基类表成员数据和虚函数体可以画出对象内存模型图:
上图中vfdd 出现的位置跟继承的顺序有关,如果DD先继承的是B2,那么它将跟在vfb2 的下面。
注意:如果没有虚继承,则虚函数表会合并,一个类只会存在一个虚函数表和一个虚函数表指针(同个类的对象共享),当然也不会有虚基类表和虚基类表指针的存在。
但如果是钻石继承,那么是会存在两份虚函数表和两份虚函数表指针的。
参考:
《深入探索C++对象模型》
C++ primer 第四版
Effective C++ 3rd
C++编程规范
转载自http://blog.csdn.net/jnu_simba/article/details/9319019
相关文章推荐
- [置顶] 从零开始学C++之虚继承和虚函数对C++对象内存模型造成的影响(类/对象的大小)
- [置顶] 从零开始学C++之虚继承和虚函数对C++对象内存模型造成的影响(类/对象的大小)
- 从零开始学C++之虚继承和虚函数对C++对象内存模型造成的影响
- C++之虚继承和虚函数对C++对象内存模型造成的影响(类/对象的大小)
- Data语意学之虚继承和虚函数对C++对象内存模型造成的影响(类/对象的大小)
- 虚继承和虚函数对c++对象存储模型的影响(类/对象的大小)
- c++ 继承 33 虚继承对c++ 对象内存模型造成的影响
- C++ 06 继承与组合 (has-a is-a) 以及类大小的计算 虚基类对内存模型的影响(不考虑虚函数)
- 三十二、C++内存布局,对象大小计算、虚函数虚继承对类内存模型的影响
- C++虚函数、虚继承、对象内存模型(转)
- 对C++对象内存模型造成的影响(类/对象的大小)
- C++虚函数、虚继承、对象内存模型
- 二十八、继承(五) 虚继承对C++对象内存模型造成的影响
- C++继承内存对象模型
- 【深入探索c++对象模型】类对象所需内存大小讨论
- 虚继承内存布局@c++对象模型
- C++对象内存布局--③测试多继承中派生类的虚函数在哪一张虚函数表中
- c++中类对象分配内存大小与虚继承的一些问题
- [C++] 虚继承对C++对象内存模型的影响
- 从零开始学C++之虚函数与多态(一):虚函数表指针、虚析构函数、object slicing与虚函数、C++对象模型图