了解你所使用的编程语言究竟是如何实现的,对于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的调试