c++對象內存布局示例詳解
前言
了解你所使用的編程語言究竟是如何實現(xiàn)的,對于C++程序員可能特別有意義。首先,它可以去除我們對于所使用語言的神秘感,使我們不至于對于編譯器干的活感到完全不可思議;尤其重要的是,它使我們在Debug和使用語言高級特性的時候,有更多的把握。當需要提高代碼效率的時候,這些知識也能夠很好地幫助我們。
簡單非多態(tài)的內存布局
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() |
|------------------------|
| |
在本示例中
- 只有數(shù)據(jù)成員存儲在堆棧中,且其聲明順序或者存儲順序的行為與編譯器強相關
- 所有其他方法(構造函數(shù),析構函數(shù)和編譯器擴展代碼)都進存儲在文本段。然后,這些方法將被調用并隱式地在調用對象的第一個參數(shù)中傳遞該指針。
this指針是一個隱含于每一個成員函數(shù)中的特殊指針。它是一個指向正在被該成員函數(shù)操作的對象,也就是要操作該成員函數(shù)的對象。this作用域是在類內部,當對一個對象調用成員函數(shù)時,編譯程序先將對象的地址賦給this指針,編譯器會自動將對象本身的地址作為一個隱含參數(shù)傳遞給函數(shù)。也就是說,即使你沒有寫this指針,編譯器在編譯的時候也是加上this的,它作為非靜態(tài)成員函數(shù)的隱含形參。被調用的成員函數(shù)函數(shù)體內所有對類成員的訪問,都會被轉化為“this->類成員”的方式。
針對第二點,我們類似于:
A x; x.printInt();
其中,X::printInt()這個行為,在編譯器中,將處理為
printInt(const X* this)
那么,x.printInt()調用處理將最終成為
printInt(&x);
同時具有虛函數(shù)和靜態(tài)數(shù)據(jù)成員的內存布局
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() |
|------------------------|
| |
- 所有非靜態(tài)數(shù)據(jù)成員都按照聲明的順序將空間放入堆棧中,與前面的示例順序相同。
- 靜態(tài)數(shù)據(jù)成員將空間放入內存的數(shù)據(jù)段中。使用范圍解析運算符(即::)進行的訪問。但是在編譯之后,就沒有像作用域和名稱空間那樣的東西了。因為,它的名稱只是由編譯器執(zhí)行,所以所有內容都由其絕對或相對地址引用。
- 靜態(tài)數(shù)據(jù)成員將空間放入內存的數(shù)據(jù)段中。使用范圍解析運算符(即::)進行的訪問。
- 靜態(tài)方法進入文本段,并通過作用域解析運算符進行調用。
- 對于virtual關鍵字,編譯器會自動將指向虛擬表的指針(vptr)插入對象內存表示中。通常,虛擬表是在數(shù)據(jù)段中為每個類靜態(tài)創(chuàng)建的,但它也取決于編譯器的實現(xiàn)。
- 在虛擬表中,第一個條目指向type_info對象,該對象包含與當前基類和其他基類的DAG(有向無環(huán)圖)相關的信息(如果從這些基類派生的信息)。
繼承對象的內存布局
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 |
| |
- 在繼承模型中,基類和數(shù)據(jù)成員類是派生類的子對象。
- 編譯器會在類的構造函數(shù)中生成具有所有重寫的虛擬功能和為_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 |
| |
在多繼承層次結構中,創(chuàng)建的虛擬表指針(vptr)的確切數(shù)目將為N-1,其中N代表類的數(shù)目。
如果嘗試使用任何基類指針調用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 | | |------------------|
- 具有一個或多個虛擬基類的派生類的內存表示形式分為兩個區(qū)域:不變區(qū)域和共享區(qū)域。
- 不變區(qū)域內的數(shù)據(jù)與對象的起始位置保持固定的偏移量,而與后續(xù)派生無關。
- 共享區(qū)域包含虛擬基類,并且隨后續(xù)派生和派生順序而波動。
總結
了解內存布局,對我們的項目開發(fā)會提供很大的便利,比如對coredump的調試
到此這篇關于c++對象內存布局的文章就介紹到這了,更多相關c++對象內存布局內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
java 中ArrayList與LinkedList性能比較
這篇文章主要介紹了java 中ArrayList與LinkedList性能比較的相關資料,需要的朋友可以參考下2017-03-03
C語言實現(xiàn)各種排序算法實例代碼(選擇,冒泡,插入,歸并,希爾,快排,堆排序,計數(shù))
排序算法是算法之中相對基礎的,也是各門語言的必學的算法,這篇文章主要介紹了C語言實現(xiàn)各種排序算法(選擇,冒泡,插入,歸并,希爾,快排,堆排序,計數(shù))的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下2021-10-10

