C++ 多重继承之内存存储

C++ 之多重继承

1. C++中class与struct。

在C++里面,class与struct没有本质的区别,只是class的默认权限是private,而struct则是public。这个概念也揭示了一点:class和struct在内部存储结构上是一致的。所以我们可以利用这一点来探讨class的实现原理。我们可以将class转换成对应的struct对象,通过struct的简单性来展示class的内存存储结构。

2. 关于class的基本内存结构

class包括成员变量和成员函数。对于成员变量,其结构和struct的结构是一致的,即按照声明的顺序,安排每个成员的内存位置。对于成员函数,如果是非虚函数(包括普通函数和静态函数),他们实际上同其他函数没有区别。对于非静态非虚函数,默认隐藏了this参数。当编译时,编译器将函数地址直接编译进去。因此,这类函数没有动态能力。对于虚函数,其函数地址将存在类this指针关联的虚函数内(参见上一篇文章),在运行时,从虚函数表内取得地址后再调用。

这样一个不带虚函数的类的内存结构,等同与一个类似的struct,而带虚函数的类的内存结构,等同于一个带有虚函数指针的struct。我用伪代码可以清晰的表示出来:

[cpp] view plaincopy
  1. class ClassA {  
  2.    int a;  
  3.    flaot b;  
  4.    void *c;  
  5. };  
  6. //等同于  
  7. struct StructA {  
  8.    int a;  
  9.    float b;  
  10.    void *c;  
  11. <span style="font-family:Arial, Helvetica, sans-serif;">};  
  12.   
  13. //带虚函数的类</span><pre name="code" class="plain">offset A::a=0, offset B::a=8, offset B::b=12  
  14. b=0x7fffad613130, pa=0x7fffad613138  

class ClassAWithVirtual { int a; float b; void* c; virtual ~ClassAWithVirtual();};//等同于struct StructAWithVirtual { VTable *vtable; //包括虚函数表的vtable int a; float b; void *c;};

 


3. class的单线继承

单线继承是很简单也很容易理解的一种继承方式。如果有class B继承自class A。那么,在class B的低地址部分,是class A的成员空间。这样class B可以直接转换为class A。

如果class A有虚函数,那么class B必须也有虚函数表。那么,如果class A没有虚函数,而class B却有虚函数,那么这个时候的内存分布应该是什么样呢?class B还能否直接转换为class A呢?

[cpp] view plaincopy
  1. #include <stdio.h>  
  2. #include <stddef.h>  
  3.   
  4. #define OFFSET(X,m) (offsetof(X, m))  
  5.   
  6. class A {  
  7. public:  
  8.     int a;  
  9. };  
  10.   
  11. class B : public A{  
  12. public:  
  13.     int b;  
  14.     virtual ~B() { }  
  15. };  
  16.   
  17. int main() {  
  18.     printf("offset A::a=%d, offset B::a=%d, offset B::b=%d\n", OFFSET(A,a), OFFSET(B, a), OFFSET(B, b));  
  19.   
  20.     B b;  
  21.     A *pa = &b;  
  22.     printf("b=%p, pa=%p\n", &b, pa);  
  23.   
  24.     return 0;  
  25. }  

在G++ 4.6 ubuntu 10.04下,输出的结果是

[cpp] view plaincopy
  1. offset A::a=0, offset B::a=8, offset B::b=12  
  2. b=0x7fffad613130, pa=0x7fffad613138  
很明显的可以看到,当B有虚函数时,B必须在内存的开始处添加一个8字节(64位系统)的虚函数表指针。这个时候,在class B内,A的成员变量不能从偏移为0的位置开始了。



3. 不带虚函数的C++的多重继承类的内存分布

一般情况下,如果一个类继承自两个类以上,那么,它的内存分布会像垒砖头那样一层一层的添加上去。比如

[cpp] view plaincopy
  1. #include <stdio.h>  
  2. #include <stddef.h>  
  3.   
  4.   
  5. class A {  
  6. public:  
  7.     int a;  
  8. };  
  9.   
  10.   
  11. class B {  
  12. public:  
  13.     int b;  
  14. };  
  15.   
  16.   
  17. class C : public A, public B{  
  18. public:  
  19.     int c;  
  20. };  
  21.   
  22.   
  23. int main() {  
  24.     printf("Offset: C::a=%d, C::b=%d, C::c=%d\n", offsetof(C, a), offsetof(C, b), offsetof(C, c));  
  25.     C c;  
  26.     A *pa = &c;  
  27.     B *pb = &c;  
  28.   
  29.   
  30.     printf("c=%p, pa=%p, pb=%p\n", &c, pa, pb);  
  31.       
  32.     return 0;  
  33. };  

输出结果是

[cpp] view plaincopy
  1. Offset: C::a=0, C::b=4, C::c=8  
  2. c=0x7fffc90bbab0, pa=0x7fffc90bbab0, pb=0x7fffc90bbab4  
这个与预想的很相似,它的等价结构是

[cpp] view plaincopy
  1. struct C {  
  2.    struct A a;  
  3.    struct B b;  
  4.    int c;  
  5. };  
的确像砖头一样,先放A,在放B,最后C的成员放上去。


那么,再考虑一种情况,如果是这样一种继承方式:

                         A

                      /     \       

                    B      C

                     \      /

                       D

那么A的成员在D内部是一份还是两份?

[cpp] view plaincopy
  1. #include <stdio.h>  
  2. #include <stddef.h>  
  3.   
  4. class A {  
  5. public:  
  6.     int a;  
  7. };  
  8.   
  9. class B : public A{  
  10. public:  
  11.     int b;  
  12. };  
  13.   
  14. class C : public A {  
  15. public:  
  16.     int c;  
  17. };  
  18.   
  19. class D : public B, public C {  
  20. public:  
  21.    int d;  
  22. };  
  23.   
  24. int main() {  
  25.     printf("Offset: D::a=%d, D::b=%d, D::c=%d, D::d\n", offsetof(D, B::a), offsetof(D, b), offsetof(D, c), offsetof(D, d));  
  26.     D d;  
  27.     B *pb = &d;  
  28.     C *pc = &d;  
  29.     A *pa_b = (A*)pb;  
  30.     A *pa_c = (A*)pc;  
  31.   
  32.     printf("d=%p, pa_b=%p, pa_c=%p, pb=%p, pc=%p, d.B::a=%p, d.C::a=%p\n", &d, pa_b, pa_c, pb, pc, &d.B::a, &d.C::a);  
  33.       
  34.     return 0;  
  35. };  
先看一下输出结果:

[cpp] view plaincopy
  1. Offset: D::a=0, D::b=4, D::c=12, D::d  
  2. d=0x7fffad730f30, pa_b=0x7fffad730f30, pa_c=0x7fffad730f38, pb=0x7fffad730f30, pc=0x7fffad730f38, d.B::a=0x7fffad730f30, d.C::a=0x7fffad730f38  
事实上,如果我直接写b.a是错误的,因为编译器不知到应该选择那个a。同样的,如果写A *pa = &d也是错误的。

结合输出结果,class D内部仍然等同于

[cpp] view plaincopy
  1. struct D {  
  2.    struct B b;  
  3.    struct C c;  
  4.    int d;  
  5. };  
而且存在两份A,这两份A分别包含在B和C内部。在使用时,必须正确指出是那个。
 

由此可见,不管继承层次有多深,C++总是按照这种垒砖头的方式叠加。如果有祖先类内部有重复包含,那么,C++也会重复包含相同的内容。

这也提醒我们,多重继承不能太复杂,否则就很难搞清楚其结构关系了。


4. 带虚函数的类的多重继承的内存分布

带虚函数的情况下,情况会变得非常复杂。首先,对于最简单的一种继承方式

                         A      B

                            \   /
                             C


我们需要分好几种情况来考虑:

   1、A B虚 C非虚

   2、A B 非虚,C虚
   3、A B 其中一个虚,C虚

   4、A B C 都虚

4.1 A B 虚 C非虚

如果只有A虚, 按照默认的规则,A的内存会被安排在偏移0处。这个时候,A的虚函数表也就是C的虚函数表。

如果只有B虚,因为B的内存会被安排在A之后,那么,B的虚函数表应该在B所在位置,C没有虚函数表。


4.2 A B 不虚,C虚

这种情况下,虚函数表应该在偏移0处,然后才是A和B的内存结构。我们来验证一下:


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/463973.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

hdu 3488

可以作为KM 二分图最大权匹配模板 View Code #include <stdio.h>#include <iostream>#include <string.h>using namespace std;const int N210;const int inf0x2fffffff;const int Max20000;int match[N],n,m,lack,w[N][N],lx[N],ly[N];bool vx[N],vy[N];bo…

Find The Multiple POJ - 1426 (BFS)

题目大意 给定一个整数&#xff0c;寻找一个只有0,1构成的十进制数使得这个数能够整除这个整数 解法 直接bfs第一位放入1&#xff0c;之后每一位放入1或者0 代码 #include <iostream> #include <queue> using namespace std; int n; void bfs() {queue<long lon…

心情不好,我就这样写代码

在 GitHub 上有一个项目&#xff0c;它描述了「最佳垃圾代码」的十九条关键准则。从变量命名到注释编写&#xff0c;这些准则将指导你写出最亮眼的烂代码。为了保持与原 GitHub 项目一致的风格&#xff0c;下文没有进行转换。读者们可以以相反的角度来理解所有观点&#xff0c;…

C++中默认构造函数使用时的要点

最近写代码的时候发现一个奇怪的现象&#xff1a;当我声明一个无参构造函数时&#xff0c;如果后面加上括号&#xff0c;声明出的对象就不能显示。比如下面的代码&#xff1a; [cpp] view plaincopy #include <stdio.h> class Test { public: Test() { …

一个立即关闭显示器的小软件(Masm开发,只有3KB大小)

我们在用电脑听歌或者下载网络资源时&#xff0c;经常都是不需要开着显示器的&#xff0c;这样不仅可以省电&#xff0c;最重要的是可以延长显示器的使用寿命&#xff0c;当然&#xff0c;对于笔记本电脑的电池省电也是很重要的。另外&#xff0c;对于液晶显示器&#xff0c;由…

动态规划之91 decode ways

题目链接&#xff1a;https://leetcode-cn.com/problems/decode-ways/description/ 参考&#xff1a;https://www.jianshu.com/p/5a604070cd11 题目大意&#xff1a;将一串数字&#xff0c;编码成A-Z的字符串。因为12-->L&#xff0c;或者12-->AB。所有12转成字符串一共有…

SQL Server中的STUFF函数的使用

STUFF ( character_expression , start , length ,character_expression ) 参数 character_expression 一个字符数据表达式。character_expression 可以是常量、变量&#xff0c;也可以是字符列或二进制数据列。start 一个整数值&#xff0c;指定删除和插入的开始位置。如果 st…

递归是会更秀strtok

前几天发的字符串反转题目&#xff0c;后面有一个新同学用了递归的方法来实现&#xff0c;看了下&#xff0c;真的是很秀。之前字符串反转的题目代码如下#include "stdio.h" #include "string.h" char input[] {"the sky is blue cris 1212321 apple…

判断输入的IP地址是否合法

判断输入的IP地址是否合法&#xff0c;ip地址的值在0~255之间&#xff0c;先把输入的IP的地址转换为一个字符串。 #define LEN (sizeof(xx)/sizeof(xx[0])) const char *xx[] {"192.168.1.1", "10.0.0.1", "127.256.0.1", "iugerjiogjioe…

偶作无题词一首

偶作无题词一首 读史籍&#xff0c;捉摸当初元璋。谈兴衰&#xff0c;寻思后来泽东。展抱负&#xff0c;携智巧&#xff0c;弄权谋&#xff0c;兴武功&#xff0c;万邦惊诧。立业不立身&#xff0c;立言不立行&#xff0c;枭雄本色。搏四海&#xff0c;威肃九州&#xff0c;建基…

ios开发网络篇—HTTP协议 - 转

一.URL 1.基本介绍 URL的全称是Uniform Resource Locator(统一资源定位符) &#xff0c;通过1个URL&#xff0c;能找到互联网唯一的1个资源 &#xff0c;URL就是资源的地址&#xff0c;位置&#xff0c;互联网上的每个资源都有一个唯一的URL 2.URL中常见的协议 (1)HTTP&#…

总结的一些内存问题

前言之前在实习时&#xff0c;听了 OOM 的分享之后&#xff0c;就对 Linux 内核内存管理充满兴趣&#xff0c;但是这块知识非常庞大&#xff0c;没有一定积累&#xff0c;不敢写下&#xff0c;担心误人子弟&#xff0c;所以经过一个一段时间的积累&#xff0c;对内核内存有一定…

云计算-从基础到应用架构系列-云计算的演进

为什么80%的码农都做不了架构师&#xff1f;>>> 开篇 本篇是主要讲述云计算的发展历程&#xff0c;由于云计算本身提出来也不是太久&#xff0c;并且其实云计算也是经过前人的一些经验总结提出&#xff0c;所以我们对之前的一 些计算机的发展史有个一定的了解&…

Linux下的基本常用命令解析

1.查进程ps命令查找与进程相关的PID号&#xff1a;ps a 显示现行终端机下的所有程序&#xff0c;包括其他用户的程序。ps -A 显示所有程序。ps c 列出程序时&#xff0c;显示每个程序真正的指令名称&#xff0c;而不包含路径&#xff0c;参数或常驻服务的标示。ps -e 此参数的效…

常用数据库脚本

SqlServer&#xff1a;1、修改数据库排序规则&#xff0c;改成中文方式(如果是英文方式的话&#xff0c;直接写insert插入中文会有问题的&#xff0c;需要使用insert into Table_1 values(N中文)的方式)Alter database master COLLATE Chinese_PRC_CI_AS;修改之后对于这个库中的…

这样理解mmap,挺有意思!

大概雍正皇帝怎么也不会想到&#xff0c;自己在西历2022年的男生和女生眼里&#xff0c;会是截然不同的两种形象。1以我对身边同学朋友的观察&#xff0c;男生们大多爱看《雍正王朝》&#xff0c;他们眼中的雍正&#xff0c;大约是个推行了“火耗归公”、“摊丁入亩”等遏制贪腐…

软件开发中的11个系统思维定律

为什么80%的码农都做不了架构师&#xff1f;>>> http://sd.csdn.net/a/20101217/284119.html?1292550154 彼得圣吉在其著作《第五项修炼》中提到的系统思维定律同样适用于软件开发。 1. 今日的问题源于昨日的解决方案&#xff08;Today’s problems come from yes…

boost.asio防止恶意空连接的方法

转载&#xff1a;http://blog.csdn.net/educast/article/details/13167847 网络服务器通常要应对一些意外情况&#xff0c;如空连接行为&#xff0c;指在遇到客户端连接后不进行任何操作&#xff0c;并很可能在大量空连接情况下导致服务器资源耗尽而无法工作。以下代码主要工作…

赢在中国 (2008-3-19)

赢在中国又换在10点&#xff0c;前面几分钟没有看到。虽然我一直不看好32号老董&#xff0c;但是我还是很好奇看看他是怎么样让自己又一次成为众人的对立面的。 我觉得32号和04号都不是心胸宽广之辈&#xff0c;但至少04号比32号真诚&#xff0c;只是他还没有修炼到32号的厚厚的…

为什么我对流程情有独钟?

写这个标题的原因是我有一个同事兼朋友&#xff0c;他的名字刚好和流程谐音&#xff0c;最近他刚离职回苏州工作&#xff0c;在球场下&#xff0c;他是我的良师益友&#xff0c;在球场上&#xff0c;他是我们可以信任的队友&#xff0c;我们不仅一次把比我们高大、速度比我们快…