目录
- 前言
- 继承对象的内存布局
- 具有多重继承和虚拟功能的对象的内存布局
- 总结
前言
了解你所使用的编程语言究竟是如何实现的,对于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的调试
到此这篇关于c++对象内存布局的文章就介绍到这了,更多相关c++对象内存布局内容请搜索<计算机技术网(www.ctvol.com)!!>以前的文章或继续浏览下面的相关文章希望大家以后多多支持<计算机技术网(www.ctvol.com)!!>!
需要了解更多c/c++开发分享c++对象内存布局示例详解,都可以关注C/C++技术分享栏目—计算机技术网(www.ctvol.com)!
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/c-cdevelopment/888327.html