当前位置: 首页 > 编程笔记 >

浅谈C++中派生类对象的内存布局

漆雕昊天
2023-03-14
本文向大家介绍浅谈C++中派生类对象的内存布局,包括了浅谈C++中派生类对象的内存布局的使用技巧和注意事项,需要的朋友参考一下

主要从三个方面来讲:

  1 单一继承

  2 多重继承

  3 虚拟继承

1 单一继承

(1)派生类完全拥有基类的内存布局,并保证其完整性。

派生类可以看作是完整的基类的Object再加上派生类自己的Object。如果基类中没有虚成员函数,那么派生类与具有相同功能的非派生类将不带来任何性能上的差异。另外,一定要保证基类的完整性。实际内存布局由编译器自己决定,VS里,把虚指针放在最前边,接着是基类的Object,最后是派生类自己的object。举个栗子:

class A
{
  int b;
  char c;
};
class A1 :public A
{
    char a;
};
int main()
{
  cout << sizeof(A) << " " << sizeof(A1) << endl;
  return 0;
}

输出是什么?

答案:

8 12

A类的话,一个int,一个char,5B,内存对齐一下,8B。A1的话,一个int,两个char,内存对齐一下,也是8B。不对吗?

我说了,要保证基类对象的完整性。那么一定要保证A1类前面的几个字节一定要与A类完全一样。也就是说,A类作为内存补齐的3个字节也是要出现在A1里面的。也就是说,A类是这样的:int(4B)+char(1B)+padding(3B)=8B,A1类:int(4B)+char(1B)+padding(3B)+char(1B)+padding(3B)=12B。

(2)虚指针怎么处理?

还是视编译器而定,VS是永远把vptr放在对象的最前边。如果基类中含有虚函数,那么处理情况与上边一样。可是,如果基类中没有虚函数而派生类有的话,那么如果把vptr放在派生类的前边的话,将会导致派生类中基类成分并不在最前边。这将带来什么问题呢?举栗:假设A不含虚,而A1含。

A *pA;
A1 obj_A1;
pA=&obj_A1;

如果A1完全包含A并且A位于A1的最前边,那么编译器只需要把&obj_A1直接赋给pA就可以了。如果不是呢?编译器就需要把&obj_A1+sizeof(vptr)赋给pA了。

2 多重继承

说结论:VS的内存布局是按照声明顺序排列内存。再举个栗子:

class point2d
{
public:
  virtual ~point2d(){};
  float x;
  float y;
};
class point3d :public point2d
{
  ~point3d(){};
  float z;
};
class vertex
{
public:
  virtual ~vertex(){};
  vertex* next;
};
class vertex3d :public point3d, public vertex
{
  float bulabula;
};


int _tmain(int argc, _TCHAR* argv[])
{
  cout << sizeof(point2d) << " " << sizeof(point3d) << " " << sizeof(vertex) << " " << sizeof(vertex3d) << endl;
  return 0;
}

输出: 12 16 8 24。

内存布局:

point2d: vptr(4)+x(4)+y(4)=12B

point3d: vptr+x+y+z=16B

vertex: vptr+next=8B

vertex3d: vptr+x+y+z+vptr+next+bulabula=28B

为什么需要多个虚指针?请往下看。

3 虚拟继承

(1)为什么要有“虚继承”这样的机制

简单讲,虚继承是为也防止“diamond”继承所带来的问题。也就是类A1、A2都继承于A,类B又同时继承于A1、A2。这样一来,类B中就有两份类A的成员了,这样的程序无法通过编译。我们改成这样的形式:

class A
{
public:
  int a;
  virtual ~A(); 
  virtual void fun(){cout<<"A"<<endl;}
};
class A1 :public virtual A
{
public:
  int a1;
  virtual void fun(){cout<<"A1"<<endl;}
};
class A2 :public virtual A
{
public:
  int a2;
  virtual void fun(){cout<<"A2"<<endl;}
}; 

class B :public A1,public A2 {
public:
  int b;
  virtual void fun(){cout<<"B"<<endl;}
  virtual void funB(){};
};

这样就能防止这样的事情发生。

(2)虚拟继承与普通继承的区别:

普通继承使得派生类每继承一个基类便拥有一份基类的成员。而虚拟继承会把通过虚拟继承的那一部分,放在对象的最后。从而使得只拥有一份基类中的成员。虚拟对象的偏移量被保存在Derived类的vtbl的this指向的上一个slot。比较难理解。下面我给你个栗子。

(3)虚拟继承的内存布局:

每个派生类会把其不变部分放在前面,共享部分放在后面。

上面四个类的大小是怎样的呢?

int _tmain(int argc, _TCHAR* argv[])
{
  cout << sizeof(A) << " " << sizeof(A1) << " " << sizeof(A2) << " " << sizeof(B) << endl;
  return 0;
}

输出:8 16 16 28

内存布局:

    A: vptr+a=8B

    A1: vptr+a1+vptrA+a=16B

    A2: vptr+a2+vptrA+a=16B

    A3: vptr+a1+vptrA2+a2+b+vptrA+a=28B

上个草图:

那究竟为什么需要多个虚指针?将对象内存布局和虚表结构搞清楚之后,答案是不是呼之欲出呢?

是的,因为这样可以保证在将子类指针/引用转换成基类指针时编译器可以直接根据对像的内存布局进行偏移,从而使得指向的第一个内容为虚指针,进而实现多态(根据静态类型执行相应动作)。

以上就是小编为大家带来的浅谈C++中派生类对象的内存布局全部内容了,希望大家多多支持小牛知识库~

 类似资料:
  • 本文向大家介绍浅谈C++对象组合,包括了浅谈C++对象组合的使用技巧和注意事项,需要的朋友参考一下 以上所述就是本文的全部内容了,希望大家能够喜欢。

  • 本文向大家介绍浅谈C++ 类的实例中 内存分配详解,包括了浅谈C++ 类的实例中 内存分配详解的使用技巧和注意事项,需要的朋友参考一下 一个类,有成员变量:静态与非静态之分;而成员函数有三种:静态的、非静态的、虚的。 那么这些个东西在内存中到底是如何分配的呢? 以一个例子来说明:  这是我的一段测试代码, 运行结果是:    我有疑问如下: (1)C++中,应该是对象才会被分配内存空间吧??为什么

  • 我学会了通过存储基类指针将派生类指针存储在基类向量中: 但是如果我有一个抽象基类: 从中派生出另外两个抽象类。 以及来自二级抽象类的其他几个派生类: 是否有可能将它们全部存储在多态性载体中?和往常一样,我做了以下工作: 但是如何将两个多态向量存储在基类向量中呢?

  • 本文向大家介绍浅谈C# 类的继承,包括了浅谈C# 类的继承的使用技巧和注意事项,需要的朋友参考一下 继承 一个类可以继承自另一个类。在 C#中,类与类之间只存在单一继承。也就是说,一个类的直接基类只能有一个。当类与类之间实现继承的时候,子类可以将它的直接基类的所有成员当做自己的成员,除了类的静态构造方法、实例构造方法和析构方法。但是,虽然基类的所有成员都可以当做子类的成员,但是如果基类的成员设置了

  • 本文向大家介绍浅谈C#中ListView类的用法,包括了浅谈C#中ListView类的用法的使用技巧和注意事项,需要的朋友参考一下 一、ListView类   1、常用的基本属性: (1)FullRowSelect:设置是否行选择模式。(默认为false) 提示:只有在Details视图该属性才有意义。 (2)GridLines:设置行和列之间是否显示网格线。(默认为false)提示:只有在Det

  • 本文向大家介绍浅谈Python 中整型对象的存储问题,包括了浅谈Python 中整型对象的存储问题的使用技巧和注意事项,需要的朋友参考一下 在 Python 整型对象所存储的位置是不同的, 有一些是一直存储在某个存储里面, 而其它的, 则在使用时开辟出空间. 说这句话的理由, 可以看看如下代码: 由上面的代码可知, 整型 5 是一直存在的, 而整型 500 不是一直存在的. 那么有哪些整数是一直存