C 物件的記憶體布局

2021-07-12 03:22:46 字數 4955 閱讀 7595

主要有三個因素對物件的記憶體布局有較大影響:類成員型別(static成員變數,virtual成員函式);繼承方式;記憶體對齊。以下分別詳細說明了具體的影響。

一、static與virtual對記憶體布局的影響

物件的記憶體分布與類的成員有關,static成員變數與非static成員變數會造成不同的記憶體分布,virtual成員函式與非virtual成員函式會造成不同的記憶體分布。具體而言,物件的記憶體只包含類的非static成員變數,當有virtual成員函式出現時,會多包含乙個指向虛函式表的指標。

1.       類只包含非static成員變數與非virtual成員函式

該類包含兩非static成員變數,乙個非virtual成員函式。

class a;

voidprinta()a;

該類的物件a的記憶體大小為8(假定int型、指標的大小為4個位元組,下同),具體分布如圖:

int a

int b

說明非static成員變數被放置在物件中,而非virtual成員函式並不包含在物件中。

2.       類包含static成員變數但不包含virtual成員函式

該類包含兩非static成員變數,乙個static成員變數,乙個非virtual成員函式。

class a;

voidprinta()a;

該類的物件a的記憶體大小為8,具體分布如圖:

int a

int b

說明static成員變數不包含在物件中。這是很容易理解的,因為static成員變數為該類的所有物件共有,若每個物件都儲存了static變數,那麼修改該變數的值將會十分困難,因為需要修改該類所有物件中的值。

3.       類包含static成員變數與virtual成員函式

該類包含兩非static成員變數,乙個static成員變數,乙個非virtual成員函式和乙個virtual成員函式。

class a;

voidprinta()a;

該類的物件a的記憶體大小為12位元組,具體分布如圖:

vptr

int a

int b

當類中出現virtual成員函式是,物件會增加乙個指標(vptr),該指標指向一張虛函式表(vtbl)。注意,當有多個virtual成員函式時還是只有乙個指標,這是的虛函式表會有多個指向函式位址的項。有關虛函式表的內容在中有簡單介紹。

總得來說,物件的記憶體中一般只包含非static成員變數,成員函式與static成員變數並不在物件中。當出現virtual成員函式時會增加乙個vptr指標。這是容易理解的,每個物件都應該有自己能夠與其他物件區分開來的非static成員變數(當兩個物件的成員變數相同時,這兩個物件是相等的。例如表示學生的物件中,當兩個物件的學號、名字、學院等屬性至少有乙個不相同時,我們才會說這表示兩個不同的學生)。而static成員變數與成員函式是該類所有物件所共有的,為了節省空間或操作簡便當然不應該分布存放在每個物件中。正因為乙個類的所有物件呼叫成員函式時呼叫的都是同乙個位置的函式,所以才會需要傳遞this指標(隱式完成)來保證操作的變數是相應物件的成員變數(若每個物件分別都儲存了成員函式就沒有必要傳遞this指標的必要了)。

二、繼承方式對記憶體布局的影響

繼承又因為單一繼承、多重繼承、重複繼承、虛擬繼承等不同繼承方式有不同的記憶體布局。

1.       單一繼承

下述例子中,基類包含兩個成員變數,乙個非virtual成員函式,兩個virtual成員函式,其中乙個被子類覆蓋。子類也包含兩個成員變數,並覆蓋了基類的非virtual成員函式和其中乙個virtual成員函式。

class base

void printa()

void printa()a;

該類的物件記憶體大小為20位元組。

vptr

int base_a

int base_b

int derived_a

int derived_b

該類的物件中首先由乙個指向虛函式表的指標(vptr),然後是繼承自基類的成員變數,最後類自身的成員變數。

2.       多重繼承

下述例子中,共有兩個基類,每個基類包含兩個成員變數,乙個非virtual成員函式,兩個virtual成員函式,其中乙個被子類覆蓋。子類也包含兩個成員變數,並覆蓋了基類的非virtual成員函式和其中乙個virtual成員函式。

class base1

void printa()

void printa()

void printa()a;

該類的物件記憶體大小為32位元組。

vptr

int base1_a

int base1_b

vptr

int base2_a

int base2_b

int derived_a

int derived_b

該類的物件中首先有繼承自基類base1指向虛函式表的指標(vptr),後面是繼承自基類base1的成員變數,然後繼承自基類base2指向虛函式表的指標(vptr),後面是繼承自基類base2的成員變數,最後是類自身的成員變數。該例說明了派生類有多少個具有virtual成員函式的基類,就有相應多個虛函式表,而在物件的記憶體中就會有相應多個指向對應虛函式表的指標。

3.       重複繼承

下述例子中,基類包含兩個成員變數,乙個非virtual成員函式,兩個virtual成員函式,其中乙個被子類覆蓋。該基類派生了兩個子類,base1,base2子類也包含兩個成員變數,並覆蓋了基類的非virtual成員函式和其中乙個virtual成員函式。base1,base2共同派生了最後的子類。

class base

void printa()

void printa()

void printa()

void printa()a;

該類的物件記憶體大小為48位元組。

vptr

int base_a

int base_b

int base1_a

int base1_b

vptr

int base_a

int base_b

int base2_a

int base2_b

int derived_a

int derived_b

該類的物件中首先有繼承自基類base1指向虛函式表的指標(vptr),後面是繼承自基類base1的成員變數,base1的成員變數包括兩個繼承自base的成員變數和自身定義的兩個成員變數;然後是與base2相關的資料與base1類似,最後是類自身的成員變數。該例說明了派生類有多個基類而基類又有相同的父類時,派生類將重複包含這個父類(例子中包含了兩次base類的成員變數)。

4.       虛擬繼承

下述例子與3中類似,不同的是base1與base2繼承base類是為虛繼承。

class base{};

class base1:virtual public base{};

class base2:virtual public base{};

class derived:public base1,public base2{};

該類的物件記憶體大小為44位元組。

vptr

int base1_a

int base1_b

vptr

int base2_a

int base2_b

int derived_a

int derived_b

vptr

int base_a

int base_b

該類的物件中首先有繼承自基類base1指向虛函式表的指標(vptr),後面是繼承自基類base1的自身定義的兩個成員變數;然後是與base2相關的資料與base1類似;在然後是類自身的成員變數;最後是base1、base2共同的基類base的虛函式表的指標(vptr),後面是base類的兩個成員變數。該例說明了虛繼承時,派生類只包含乙個父類的成員變數。

三、記憶體補齊對記憶體布局的影響

類中會有許多不同的資料型別,編譯器為了更方便得查詢資料,會自動進行對齊。每個成員變數都會按照起始位置為自身大小整數倍位址的規則進行對齊,而最後物件會將記憶體大小補齊為最大的成員變數的整數倍。下面是幾個補齊的例子:

1、class tem{

char a; int b; char c;

這個類的大小為12位元組,具體分布如下:ab

c 首先char a占用乙個位元組,偏移量為0,int b占用4個位元組,且其實位置需要為4的整數倍,故補齊3個位元組後才是b的起始位元組,char c占用1個位元組,偏移量為8,共9個位元組。最後補齊為最大的變數(int)大小的整數倍12個位元組。

2、class tem{

char a; short b; int c; char d;

這個類的大小為12位元組,具體分布如下:ab

cd首先char a占用乙個位元組,偏移量為0,short b占用2個位元組,且起始位置需要為2的整數倍,故偏移量為2,int b占用4個位元組,且其實位置需要為4的整數倍,故偏移量為4正好不需要補齊,char c占用1個位元組,偏移量為8,共9個位元組。最後補齊為最大的變數大小的整數倍12個字

3、class tem{

char a; char d; short b; int c;

這個類的大小為8位元組,具體分布如下:ad

bc首先char a占用乙個位元組,偏移量為0,char b占用乙個位元組,偏移量為1,short b占用2個位元組,偏移量為2不需要補齊,int b占用4個位元組,且起始位置需要為4的整數倍,故偏移量為4正好不需要補齊,共8個位元組。正好為最大變數int型的整數倍,故最終大小為8位元組。

4、class tem{

char a; short b; char d; int c;

這個類的大小為12位元組,具體分布如下:ab

dc首先char a占用乙個位元組,偏移量為0, short b占用2個位元組,偏移量為2補齊乙個位元組,char d占用1個位元組,偏移量為4,int b占用4個位元組,故偏移量為8補齊三個位元組,共12個位元組。正好為最大變數int型的整數倍,故最終大小為12位元組。

從以上可以看出,將變數宣告順序按從小到大排列能夠最大化記憶體空間的利用。

C 物件的記憶體布局

一篇寫的比較好的部落格 這篇文章中主要想說以下幾個問題 1 如何通過物件獲得虛函式表中虛函式的位址 2 分幾種情況討論記憶體布局 1 單一繼承 2 多重繼承 3 重複繼承 4 鑽石虛擬繼承 為了解決重複繼承中出現問題而產生的虛擬繼承 1 虛函式主要是通過一張虛函式的位址表來實現的,簡稱v table...

C 物件的記憶體布局

記憶體布局是屬於較深層次的知識,很多問題往深了講都是不清楚記憶體布局的原理。最近讀到一本書,裡面講了一部分c 物件的記憶體布局,讓我對很多以前的問題都豁然開朗了。書上篇幅較大,我加上自己的理解總結了下。分為三部分 簡單物件,單繼承,多繼承 非靜態成員變數和虛函式是決定類大小的唯一兩個因素 非靜態成員...

c 物件的記憶體布局

以下均為linux64位編譯器上實驗資料,指標大小為8位元組 1.空類 class n n n sizeof n 等於1編譯器會安插乙個char位元組以保證其每個例項都有唯一的位址 2.無虛函式無繼承類 class a a a sizeof a 等於8class和struct一樣會滿足記憶體對齊,a...