為了便於分析和觀察對象的內存布局,我把代碼生成時的結構成員對齊選項設置為1字節,默認為8字節。如果你在自己的工程下編譯文中的代碼,請做同樣的設置。因為我寫了一些函數打印對象中的布局信息,如果對象選項不是1字節,運行這些代碼會出現指針異常錯誤。
普通類對象的內存布局
首先我們從普通類對象的內存布局開始。c000為一個空類,定義如下:
struct c000
{};
運行如下代碼打印它的大小及對象中的內容。
print_size_detail(c000)
結果為:
the size of c000 is 1
the detail of c000 is cc
可以看到它的大小為1字節,這是一個占位符。我們可以看到它的值是0xcc。在debug模式下,這表示是由編譯器插入的調試代碼所初始化的內存。在release模式下可能是個隨機值,我測試時值為0x00。
定義兩個類,c010和c011如下:
struct c010
{
c010() : c_(0x01) {}
void foo() { c_ = 0x02; }
char c_;
};
struct c011
{
c011() : c1_(0x02), c2_(0x03) {}
char c1_;
char c2_;
};
運行如下代碼打印它們的大小及對象中的內容。
print_size_detail(c010)
print_size_detail(c012)
結果為:
the size of c010 is 1
the detail of c010 is 01
the size of c011 is 2
the detail of c011 is 02 03
我們從對象的內存輸出中可以看到,它們的值就是我們在構造函數中賦的值,c010為0x01,c011為0x0203。大小分別為1、2。
定義c012類。
struct c012
{
static int sfoo() { return 1; }
int foo() { return 1; }
char c_;
static int i_;
};
int c012::i_ = 1;
在這個類中我們加入了一個靜態數據成員,一個普通成員函數和一個靜態成員函數。
運行如下代碼打印它的大小及對象中的內容。
print_size_detail(c012)
結果為:
the size of c012 is 1
the detail of c012 is cc
可以看到它的大小還是1字節,值為0xcc是因為我們沒有初始化它,原因前面說過了。
從上面的結果我們可以映證,普通成員函數,靜態成員函數,及靜態成員變量皆不會在類的對象中有所表示,成員函數和對象的關聯由編譯器在編譯時處理,正如我們會在後面看到的那樣,編譯器會在編譯時決議出正確的普通成員函數地址,並將對象的地址以this指針的方式,做為第一個參數傳遞給普通成員函數,以此來進行關聯。靜態成員函數類似於全局函數,不和具體的對象關聯。靜態成員變量也一樣。靜態成員函數和靜態成員變量和普通的全局函數及全局變量不同之處在於它們多了一層名字限定。
普通繼承類對象的內存布局
下面看看普通繼承類對象的內存布局。
定義一個空類c014從c011繼承,再定義c015也是一個空類從c010和c011繼承。
struct c010
{
c010() : c_(0x01) {}
void foo() { c_ = 0x02; }
char c_;
};
struct c011
{
c011() : c1_(0x02), c2_(0x03) {}
char c1_;
char c2_;
};
struct c014 : private c011
{};
struct c015 : public c010, private c011
{};
運行如下代碼打印它們的大小及對象中的內容。
print_size_detail(c014)
print_size_detail(c015)
結果為:
the size of c014 is 2
the detail of c014 is 02 03
the size of c015 is 3
the detail of c015 is 01 02 03
c014的大小為2字節,也就是c011的大小,對象的內存值也是在c011的構造函數中初始化的兩個值0x0203。c015的大小為3字節,也就是c010和c011的大小之和,對象的內存值為0x010203。
這裡我們可以發現父類的成員變量悉數被子類繼承,並且於繼承方式(公有或私有)無關,如c015是私有繼承自c011。繼承方式只影響數據成員的“能見度”。子類對象中屬於從父類繼承的成員變量由父類的構造函數初始化。通常會調用默認構造函數,除非子類在它的構造函數初始化列表中顯式調用父類的非默認構造函數。如果沒有指定,而父類又沒有缺省構造函數,則會產生編譯錯誤。
我們可以再加一層繼承來驗證一下。定義類c016,從c015繼承,並有自己的4字節int成員變量。
struct c016 : c015
{
c016() : i_(1) {}
int i_;
};
運行如下代碼打印它的大小及對象中的內容。
print_size_detail(c016)
結果為:
the size of c016 is 7
the detail of c016 is 01 02 03 01 00 00 00
它的大小為7字節,也就是c015的大小(也即是c010和c011的大小和)加上自身的4字節int變量之和。同樣對象的內存輸出也驗證了這一點,前三個字節為從父類繼承的,後4個字節為自身的int變量,值為1。
因此關於普通繼承,子類的對象布局為父類中的數據成員加上子類中的數據成員,多層繼承時(如c016),頂層類在前,多重繼承時則最左父類在前。