C++ 对象的内存布局(重复虚拟继承)
2016-06-28 15:21
441 查看
原文:http://blog.csdn.net/haoel/article/details/3081385
下面,让我们来看看重复虚拟继承中C++对象的内存布局的情况,假设有下面这样一个类的继承关系。
虚拟继承的出现就是为了解决重复继承中多个间接父类的问题的。钻石型的结构是其最经典的结构。也是我们在这里要讨论的结构:
上述的“重复继承”只需要把B1和B2继承B的语法中加上virtual
关键,就成了虚拟继承,其继承图如下所示:
上图和前面的“重复继承”中的类的内部数据和接口都是完全一样的,只是我们采用了虚拟继承:其省略后的源码如下所示:
class B {……};class B1 : virtual public B{……};class B2: virtual public B{……};class D : public B1, public B2{ …… };
在查看D之前,我们先看一看单一虚拟继承的情况。下面是一段在VC++2003下的测试程序:(因为VC++和GCC的内存而局上有一些细节上的不同,所以这里只给出VC++的程序,GCC下的程序大家可以根据我给出的程序自己仿照着写一个去试一试):
int** pVtab = NULL; Fun pFun = NULL; B1 bb1; pVtab = (int**)&bb1; cout << "[0] B1::_vptr->" << endl; pFun = (Fun)pVtab[0][0]; cout << " [0] "; pFun(); //B1::f1(); cout << " [1] "; pFun = (Fun)pVtab[0][1]; pFun(); //B1::bf1(); cout << " [2] "; cout << pVtab[0][2] << endl; cout << "[1] = 0x"; cout << (int*)*((int*)(&bb1)+1) <<endl; //B1::ib1 cout << "[2] B1::ib1 = "; cout << (int)*((int*)(&bb1)+2) <<endl; //B1::ib1 cout << "[3] B1::cb1 = "; cout << (char)*((int*)(&bb1)+3) << endl; //B1::cb1 cout << "[4] = 0x"; cout << (int*)*((int*)(&bb1)+4) << endl; //NULL cout << "[5] B::_vptr->" << endl; pFun = (Fun)pVtab[5][0]; cout << " [0] "; pFun(); //B1::f(); pFun = (Fun)pVtab[5][1]; cout << " [1] "; pFun(); //B::Bf(); cout << " [2] "; cout << "0x" << (Fun)pVtab[5][2] << endl; cout << "[6] B::ib = "; cout << (int)*((int*)(&bb1)+6) <<endl; //B::ib cout << "[7] B::cb = ";
其运行结果如下(我结出了GCC的和VC++2003的对比):
这里,大家可以自己对比一下。关于细节上,会在后面一并再说。
下面的测试程序是看子类D的内存布局,同样是VC++ 2003的(因为VC++和GCC的内存布局上有一些细节上的不同,而VC++的相对要清楚很多,所以这里只给出VC++的程序,GCC下的程序大家可以根据我给出的程序自己仿照着写一个去试一试):
D d; pVtab = (int**)&d; cout << "[0] D::B1::_vptr->" << endl; pFun = (Fun)pVtab[0][0]; cout << " [0] "; pFun(); //D::f1(); pFun = (Fun)pVtab[0][1]; cout << " [1] "; pFun(); //B1::Bf1(); pFun = (Fun)pVtab[0][2]; cout << " [2] "; pFun(); //D::Df(); pFun = (Fun)pVtab[0][3]; cout << " [3] "; cout << pFun << endl; //cout << pVtab[4][2] << endl; cout << "[1] = 0x"; cout << (int*)((&dd)+1) <<endl; //???? cout << "[2] B1::ib1 = "; cout << *((int*)(&dd)+2) <<endl; //B1::ib1 cout << "[3] B1::cb1 = "; cout << (char)*((int*)(&dd)+3) << endl; //B1::cb1 //--------------------- cout << "[4] D::B2::_vptr->" << endl; pFun = (Fun)pVtab[4][0]; cout << " [0] "; pFun(); //D::f2(); pFun = (Fun)pVtab[4][1]; cout << " [1] "; pFun(); //B2::Bf2(); pFun = (Fun)pVtab[4][2]; cout << " [2] "; cout << pFun << endl; cout << "[5] = 0x"; cout << *((int*)(&dd)+5) << endl; // ??? cout << "[6] B2::ib2 = "; cout << (int)*((int*)(&dd)+6) <<endl; //B2::ib2 cout << "[7] B2::cb2 = "; cout << (char)*((int*)(&dd)+7) << endl; //B2::cb2 cout << "[8] D::id = "; cout << *((int*)(&dd)+8) << endl; //D::id cout << "[9] D::cd = "; cout << (char)*((int*)(&dd)+9) << endl;//D::cd cout << "[10] = 0x"; cout << (int*)*((int*)(&dd)+10) << endl; //--------------------- cout << "[11] D::B::_vptr->" << endl; pFun = (Fun)pVtab[11][0]; cout << " [0] "; pFun(); //D::f(); pFun = (Fun)pVtab[11][1]; cout << " [1] "; pFun(); //B::Bf(); pFun = (Fun)pVtab[11][2]; cout << " [2] "; cout << pFun << endl; cout << "[12] B::ib = "; cout << *((int*)(&dd)+12) << endl; //B::ib cout << "[13] B::cb = "; cout << (char)*((int*)(&dd)+13) <<endl;//B::cb
下面给出运行后的结果(分VC++和GCC两部份)
在上面的输出结果中,我用不同的颜色做了一些标明。我们可以看到如下的几点:
1)无论是GCC还是VC++,除了一些细节上的不同,其大体上的对象布局是一样的。也就是说,先是B1(黄色),然后是B2(绿色),接着是D(灰色),而B这个超类(青蓝色)的实例都放在最后的位置。
2)关于虚函数表,尤其是第一个虚表,GCC和VC++有很重大的不一样。但仔细看下来,还是VC++的虚表比较清晰和有逻辑性。
3)VC++和GCC都把B这个超类放到了最后,而VC++有一个NULL分隔符把B和B1和B2的布局分开。GCC则没有。
4)VC++中的内存布局有两个地址我有些不是很明白,在其中我用红色标出了。取其内容是-4。接道理来说,这个指针应该是指向B类实例的内存地址(这个做法就是为了保证重复的父类只有一个实例的技术)。但取值后却不是。这点我目前还并不太清楚,还向大家请教。
5)GCC的内存布局中在B1和B2中则没有指向B的指针。这点可以理解,编译器可以通过计算B1和B2的size而得出B的偏移量。
下面,让我们来看看重复虚拟继承中C++对象的内存布局的情况,假设有下面这样一个类的继承关系。
虚拟继承的出现就是为了解决重复继承中多个间接父类的问题的。钻石型的结构是其最经典的结构。也是我们在这里要讨论的结构:
上述的“重复继承”只需要把B1和B2继承B的语法中加上virtual
关键,就成了虚拟继承,其继承图如下所示:
上图和前面的“重复继承”中的类的内部数据和接口都是完全一样的,只是我们采用了虚拟继承:其省略后的源码如下所示:
class B {……};class B1 : virtual public B{……};class B2: virtual public B{……};class D : public B1, public B2{ …… };
在查看D之前,我们先看一看单一虚拟继承的情况。下面是一段在VC++2003下的测试程序:(因为VC++和GCC的内存而局上有一些细节上的不同,所以这里只给出VC++的程序,GCC下的程序大家可以根据我给出的程序自己仿照着写一个去试一试):
int** pVtab = NULL; Fun pFun = NULL; B1 bb1; pVtab = (int**)&bb1; cout << "[0] B1::_vptr->" << endl; pFun = (Fun)pVtab[0][0]; cout << " [0] "; pFun(); //B1::f1(); cout << " [1] "; pFun = (Fun)pVtab[0][1]; pFun(); //B1::bf1(); cout << " [2] "; cout << pVtab[0][2] << endl; cout << "[1] = 0x"; cout << (int*)*((int*)(&bb1)+1) <<endl; //B1::ib1 cout << "[2] B1::ib1 = "; cout << (int)*((int*)(&bb1)+2) <<endl; //B1::ib1 cout << "[3] B1::cb1 = "; cout << (char)*((int*)(&bb1)+3) << endl; //B1::cb1 cout << "[4] = 0x"; cout << (int*)*((int*)(&bb1)+4) << endl; //NULL cout << "[5] B::_vptr->" << endl; pFun = (Fun)pVtab[5][0]; cout << " [0] "; pFun(); //B1::f(); pFun = (Fun)pVtab[5][1]; cout << " [1] "; pFun(); //B::Bf(); cout << " [2] "; cout << "0x" << (Fun)pVtab[5][2] << endl; cout << "[6] B::ib = "; cout << (int)*((int*)(&bb1)+6) <<endl; //B::ib cout << "[7] B::cb = ";
其运行结果如下(我结出了GCC的和VC++2003的对比):
GCC 3.4.4 | VC++ 2003 |
[0] B1::_vptr -> [0] : B1::f() [1] : B1::f1() [2] : B1::Bf1() [3] : 0 [1] B1::ib1 : 11 [2] B1::cb1 : 1 [3] B::_vptr -> [0] : B1::f() [1] : B::Bf() [2] : 0 [4] B::ib : 0 [5] B::cb : B [6] NULL : 0 | [0] B1::_vptr-> [0] B1::f1() [1] B1::Bf1() [2] 0 [1] = 0x00454310 ?该地址取值后是-4 [2] B1::ib1 = 11 [3] B1::cb1 = 1 [4] = 0x00000000 [5] B::_vptr-> [0] B1::f() [1] B::Bf() [2] 0x00000000 [6] B::ib = 0 [7] B::cb = B |
这里,大家可以自己对比一下。关于细节上,会在后面一并再说。
下面的测试程序是看子类D的内存布局,同样是VC++ 2003的(因为VC++和GCC的内存布局上有一些细节上的不同,而VC++的相对要清楚很多,所以这里只给出VC++的程序,GCC下的程序大家可以根据我给出的程序自己仿照着写一个去试一试):
D d; pVtab = (int**)&d; cout << "[0] D::B1::_vptr->" << endl; pFun = (Fun)pVtab[0][0]; cout << " [0] "; pFun(); //D::f1(); pFun = (Fun)pVtab[0][1]; cout << " [1] "; pFun(); //B1::Bf1(); pFun = (Fun)pVtab[0][2]; cout << " [2] "; pFun(); //D::Df(); pFun = (Fun)pVtab[0][3]; cout << " [3] "; cout << pFun << endl; //cout << pVtab[4][2] << endl; cout << "[1] = 0x"; cout << (int*)((&dd)+1) <<endl; //???? cout << "[2] B1::ib1 = "; cout << *((int*)(&dd)+2) <<endl; //B1::ib1 cout << "[3] B1::cb1 = "; cout << (char)*((int*)(&dd)+3) << endl; //B1::cb1 //--------------------- cout << "[4] D::B2::_vptr->" << endl; pFun = (Fun)pVtab[4][0]; cout << " [0] "; pFun(); //D::f2(); pFun = (Fun)pVtab[4][1]; cout << " [1] "; pFun(); //B2::Bf2(); pFun = (Fun)pVtab[4][2]; cout << " [2] "; cout << pFun << endl; cout << "[5] = 0x"; cout << *((int*)(&dd)+5) << endl; // ??? cout << "[6] B2::ib2 = "; cout << (int)*((int*)(&dd)+6) <<endl; //B2::ib2 cout << "[7] B2::cb2 = "; cout << (char)*((int*)(&dd)+7) << endl; //B2::cb2 cout << "[8] D::id = "; cout << *((int*)(&dd)+8) << endl; //D::id cout << "[9] D::cd = "; cout << (char)*((int*)(&dd)+9) << endl;//D::cd cout << "[10] = 0x"; cout << (int*)*((int*)(&dd)+10) << endl; //--------------------- cout << "[11] D::B::_vptr->" << endl; pFun = (Fun)pVtab[11][0]; cout << " [0] "; pFun(); //D::f(); pFun = (Fun)pVtab[11][1]; cout << " [1] "; pFun(); //B::Bf(); pFun = (Fun)pVtab[11][2]; cout << " [2] "; cout << pFun << endl; cout << "[12] B::ib = "; cout << *((int*)(&dd)+12) << endl; //B::ib cout << "[13] B::cb = "; cout << (char)*((int*)(&dd)+13) <<endl;//B::cb
下面给出运行后的结果(分VC++和GCC两部份)
GCC 3.4.4 | VC++ 2003 |
[0] B1::_vptr -> [0] : D::f() [1] : D::f1() [2] : B1::Bf1() [3] : D::f2() [4] : D::Df() [5] : 1 [1] B1::ib1 : 11 [2] B1::cb1 : 1 [3] B2::_vptr -> [0] : D::f() [1] : D::f2() [2] : B2::Bf2() [3] : 0 [4] B2::ib2 : 12 [5] B2::cb2 : 2 [6] D::id : 100 [7] D::cd : D [8] B::_vptr -> [0] : D::f() [1] : B::Bf() [2] : 0 [9] B::ib : 0 [10] B::cb : B [11] NULL : 0 | [0] D::B1::_vptr-> [0] D::f1() [1] B1::Bf1() [2] D::Df() [3] 00000000 [1] = 0x0013FDC4 ? 该地址取值后是-4 [2] B1::ib1 = 11 [3] B1::cb1 = 1 [4] D::B2::_vptr-> [0] D::f2() [1] B2::Bf2() [2] 00000000 [5] = 0x4539260 ? 该地址取值后是-4 [6] B2::ib2 = 12 [7] B2::cb2 = 2 [8] D::id = 100 [9] D::cd = D [10] = 0x00000000 [11] D::B::_vptr-> [0] D::f() [1] B::Bf() [2] 00000000 [12] B::ib = 0 [13] B::cb = B |
在上面的输出结果中,我用不同的颜色做了一些标明。我们可以看到如下的几点:
1)无论是GCC还是VC++,除了一些细节上的不同,其大体上的对象布局是一样的。也就是说,先是B1(黄色),然后是B2(绿色),接着是D(灰色),而B这个超类(青蓝色)的实例都放在最后的位置。
2)关于虚函数表,尤其是第一个虚表,GCC和VC++有很重大的不一样。但仔细看下来,还是VC++的虚表比较清晰和有逻辑性。
3)VC++和GCC都把B这个超类放到了最后,而VC++有一个NULL分隔符把B和B1和B2的布局分开。GCC则没有。
4)VC++中的内存布局有两个地址我有些不是很明白,在其中我用红色标出了。取其内容是-4。接道理来说,这个指针应该是指向B类实例的内存地址(这个做法就是为了保证重复的父类只有一个实例的技术)。但取值后却不是。这点我目前还并不太清楚,还向大家请教。
5)GCC的内存布局中在B1和B2中则没有指向B的指针。这点可以理解,编译器可以通过计算B1和B2的size而得出B的偏移量。
相关文章推荐
- C++ 对象的内存布局(重复继承)
- C++ 对象的内存布局(多重虚拟继承)
- USACO Combination Lock 解题日志
- C++ 对象的内存布局(单继承)
- 机器cpu与内存配置的获取
- C++虚函数表解析(高级篇)
- C++虚函数表解析(基础篇)
- leetcode 125 Valid Palindrome C++
- XOR Zero
- C语言交换变量之临时变量使用指针
- 高斯模糊算法的 C++ 实现
- C语言指针之野指针
- Android使用C/C++来保存密钥
- C++程序设计语言练习10.12 C++的普通类表示
- 网狐客户端调试
- C语言时间函数
- sublime text2 + cygwin编译C++
- C语言中.h和.c文件解析(很精彩)
- 关闭 leetcode_c++:哈希:Suduku_Solver(037)
- C++指针详解