c++对象内存布局

​了解你所使用的编程语言究竟是如何实现的,对于C++程序员可能特别有意义。首先,它可以去除我们对于所使用语言的神秘感,使我们不至于对于编译器干的活感到完全不可思议;尤其重要的是,它使我们在Debug和使用语言高级特性的时候,有更多的把握。当需要提高代码效率的时候,这些知识也能够很好地帮助我们。

简单非多态的内存布局

  • class X {
  •     int     x;
  •     float   xx;
  • public:
  •     X() {}
  •     ~X() {}
  •     void printInt() {}
  •     void printFloat() {}
  • };
  •       |                        |          
  •       |------------------------| <------ X class object memory layout
  •       |        int X::x        |
  •       |------------------------|  stack segment
  •       |       float X::xx      |       |   
  •       |------------------------|       |
  •       |                        |      \\|/
  •       |                        |    
  •       |                        |
  • ------|------------------------|----------------
  •       |         X::X()         
  •       |------------------------|       |   
  •       |        X::~X()         |       |
  •       |------------------------|      \\|/
  •       |      X::printInt()     |  text segment
  •       |------------------------|
  •       |     X::printFloat()    |
  •       |------------------------|
  •       |                        |            

在本示例中

  • 只有数据成员存储在堆栈中,且其声明顺序或者存储顺序的行为与编译器强相关
  • 所有其他方法(构造函数,析构函数和编译器扩展代码)都存储在文本段。然后,这些方法将被调用并隐式地在调用对象的第一个参数中传递该指针。

this指针是一个隐含于每一个成员函数中的特殊指针。它是一个指向正在被该成员函数操作的对象,也就是要操作该成员函数的对象。this作用域是在类内部,当对一个对象调用成员函数时,编译程序先将对象的地址赋给this指针,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参。被调用的成员函数函数体内所有对类成员的访问,都会被转化为“this->类成员”的方式。

针对第二点,我们类似于:

  • A x;
  • x.printInt();

其中,X::printInt()这个行为,在编译器中,将处理为

  • printInt(const X* this)

那么,x.printInt()调用处理将最终成为

  • printInt(&x);

同时具有虚函数和静态数据成员的内存布局

  • class X {
  •     int         x;
  •     float       xx;
  •     static int  count;
  • public:
  •     X() {}
  •     virtual ~X() {}
  •     virtual void printAll() {}
  •     void printInt() {}
  •     void printFloat() {}
  •     static void printCount() {}
  • };

其内存布局如下

  •       |                        |          
  •       |------------------------| <------ X class object memory layout
  •       |        int X::x        |
  • stack |------------------------|
  •   |   |       float X::xx      |                      
  •   |   |------------------------|      |-------|--------------------------|
  •   |   |         X::_vptr       |------|       |       type_info X        |
  •  \\|/  |------------------------|              |--------------------------|
  •       |           o            |              |    address of X::~X()    |
  •       |           o            |              |--------------------------|
  •       |           o            |              | address of X::printAll() |
  •       |                        |              |--------------------------|
  •       |                        |
  • ------|------------------------|------------
  •       |  static int X::count   |      /|\\
  •       |------------------------|       |
  •       |           o            |  data segment           
  •       |           o            |       |
  •       |                        |      \\|/
  • ------|------------------------|------------
  •       |        X::X()          | 
  •       |------------------------|       |   
  •       |        X::~X()         |       |
  •       |------------------------|       
  •       |      X::printAll()     |      \\|
  •       |------------------------|  text segment
  •       |      X::printInt()     |
  •       |------------------------|
  •       |     X::printFloat()    |
  •       |------------------------|
  •       | static X::printCount() |
  •       |------------------------|
  •       |                        |
  • 所有非静态数据成员都按照声明的顺序将空间放入堆栈中,与前面的示例顺序相同。
  • 静态数据成员将空间放入内存的数据段中。使用范围解析运算符(即::)进行的访问。但是在编译之后,就没有像作用域和名称空间那样的东西了。因为,它的名称只是由编译器执行,所以所有内容都由其绝对或相对地址引用。
  • 静态数据成员将空间放入内存的数据段中。使用范围解析运算符(即::)进行的访问。
  • 静态方法进入文本段,并通过作用域解析运算符进行调用。
  • 对于virtual关键字,编译器会自动将指向虚拟表的指针(vptr)插入对象内存表示中。通常,虚拟表是在数据段中为每个类静态创建的,但它也取决于编译器的实现。
  • 在虚拟表中,第一个条目指向type_info对象,该对象包含与当前基类和其他基类的DAG(有向无环图)相关的信息(如果从这些基类派生的信息)。

继承对象的内存布局

  • class X {
  •     int     x;
  •     string str;
  • public:
  •     X() {}
  •     virtual ~X() {}
  •     virtual void printAll() {}
  • };
  • class Y : public X {
  •     int     y;
  • public:
  •     Y() {}
  •     ~Y() {}
  •     void printAll() {}
  • };

其内存布局信息如下

  •       |                              |          
  •       |------------------------------| <------ Y class object memory layout
  •       |          int X::x            |
  • stack |------------------------------|
  •   |   |              int string::len |
  •   |   |string X::str ----------------|
  •   |   |            char* string::str |         
  •  \\|/  |------------------------------|      |-------|--------------------------|
  •       |           X::_vptr           |------|       |       type_info Y        |
  •       |------------------------------|              |--------------------------|
  •       |          int Y::y            |              |    address of Y::~Y()    |
  •       |------------------------------|              |--------------------------|
  •       |               o              |              | address of Y::printAll() |
  •       |               o              |              |--------------------------|
  •       |               o              |              
  • ------|------------------------------|--------
  •       |           X::X()             | 
  •       |------------------------------|       |   
  •       |           X::~X()            |       |
  •       |------------------------------|       
  •       |         X::printAll()        |      \\|
  •       |------------------------------|  text segment
  •       |           Y::Y()             |
  •       |------------------------------|
  •       |           Y::~Y()            |
  •       |------------------------------|
  •       |         Y::printAll()        |
  •       |------------------------------|
  •       |      string::string()        |
  •       |------------------------------|
  •       |      string::~string()       |
  •       |------------------------------|
  •       |      string::length()        |
  •       |------------------------------|
  •       |               o              |
  •       |               o              |
  •       |               o              |
  •       |                              |
展开
  • 在继承模型中,基类和数据成员类是派生类的子对象。
  • 编译器会在类的构造函数中生成具有所有重写的虚拟功能和为_vptr分配虚拟表的代码的虚拟表。

具有多重继承和虚拟功能的对象的内存布局

  • class X {
  • public:
  •     int     x;
  •     
  •     virtual ~X() {}
  •     virtual void printX() {}
  • };
  • class Y {
  • public:
  •     int     y;
  •     
  •     virtual ~Y() {}
  •     virtual void printY() {}
  • };
  • class Z : public X, public Y {
  • public:
  •     int     z;
  •     
  •     ~Z() {}
  •     void printX() {}
  •     void printY() {}
  •     void printZ() {}
  • };

内存布局如下

  •       |                              |          
  •       |------------------------------| <------ Z class object memory layout
  • stack |          int X::x            |         
  •   |   |------------------------------|                  |--------------------------|      
  •   |   |          X:: _vptr           |----------------->|       type_info Z        |
  •   |   |------------------------------|                  |--------------------------|
  •  \\|/  |          int Y::y            |                  |    address of Z::~Z()    |
  •       |------------------------------|                  |--------------------------|
  •       |          Y:: _vptr           |------|           |   address of Z::printX() |
  •       |------------------------------|      |           |--------------------------|
  •       |          int Z::z            |      |           |--------GUARD_AREA--------|    
  •       |------------------------------|      |           |--------------------------|
  •       |              o               |      |---------->|       type_info Z        |
  •       |              o               |                  |--------------------------|
  •       |              o               |                  |    address of Z::~Z()    |
  •       |                              |                  |--------------------------|
  • ------|------------------------------|---------         |   address of Z::printY() |
  •       |           X::~X()            |       |          |--------------------------|  
  •       |------------------------------|       |          
  •       |          X::printX()         |       |        
  •       |------------------------------|       |         
  •       |           Y::~Y()            |      \\|/        
  •       |------------------------------|  text segment
  •       |          Y::printY()         |                
  •       |------------------------------|                
  •       |           Z::~Z()            |                
  •       |------------------------------|                
  •       |          Z::printX()         |                
  •       |------------------------------|                
  •       |          Z::printY()         |                
  •       |------------------------------|                
  •       |          Z::printZ()         |                
  •       |------------------------------|                
  •       |               o              |                
  •       |               o              |                
  •       |                              |                
展开
  • 在多继承层次结构中,创建的虚拟表指针(vptr)的确切数目将为N-1,其中N代表类的数目。
  • 如果尝试使用任何基类指针调用Z类的方法,则它将使用相应的虚拟表进行调用。如下例子所示:
  • Y *y_ptr = new Z;
  • y_ptr->printY(); // OK
  • y_ptr->printZ(); // Not OK, as virtual table of class Y doesn't have address of printZ() method
  • 在上面的代码中,y_ptr将指向完整Z对象内类Y的子对象。
  • 结果,调用任何方法,例如使用y_ptr-> printY()。使用y_ptr的解析方式如下:
  •  ( *y_ptr->_vtbl[ 2 ] )( y_ptr )

虚继承内存布局

  • class X { int x; };
  • class Y : public virtual X { int y; };
  • class Z : public virtual X { int z; };
  • class A : public Y, public Z { int a; };

其布局如下:

  •                   |                |          
  •  Y class  ------> |----------------| <------ A class object memory layout
  • sub-object        |   Y::y         |          
  •                   |----------------|             |------------------| 
  •                   |   Y::_vptr_Y   |------|      |    offset of X   | // offset(20) starts from Y 
  •  Z class  ------> |----------------|      |----> |------------------|     
  • sub-object        |   Z::z         |             |       .....      |     
  •                   |----------------|             |------------------|  
  •                   |   Z::_vptr_Z   |------|       
  •                   |----------------|      |        
  •  A sub-object --> |   A::a         |      |      |------------------| 
  •                   |----------------|      |      |    offset of X   | // offset(12) starts from Z
  •  X class -------> |   X::x         |      |----> |------------------|          
  •  shared           |----------------|             |       .....      |           
  •  sub-object       |                |             |------------------|           
  • 具有一个或多个虚拟基类的派生类的内存表示形式分为两个区域:不变区域和共享区域。
  • 不变区域内的数据与对象的起始位置保持固定的偏移量,而与后续派生无关。
  • 共享区域包含虚拟基类,并且随后续派生和派生顺序而波动。

总结

了解内存布局,对我们的项目开发会提供很大的便利,比如对coredump的调试。

本站文章资源均来源自网络,除非特别声明,否则均不代表站方观点,并仅供查阅,不作为任何参考依据!
如有侵权请及时跟我们联系,本站将及时删除!
如遇版权问题,请查看 本站版权声明
THE END
分享
二维码
海报
<<上一篇
下一篇>>