http://dev.yesky.com/136/2317136.shtml
一個(gè)C++程序員,想要進(jìn)一步提升技術(shù)水平的話,應(yīng)該多了解一些語(yǔ)言的語(yǔ)意細(xì)節(jié)。對(duì)于使用VC++的程序員來(lái)說(shuō),還應(yīng)該了解一些VC++對(duì)于C++的詮釋。Inside the C++ Object Model雖然是一本好書,然而,書的篇幅多一些,又和具體的VC++關(guān)系小一些。因此,從篇幅和內(nèi)容來(lái)看,譯者認(rèn)為本文是深入理解C++對(duì)象模型比較好的一個(gè)出發(fā)點(diǎn)。
這篇文章以前看到時(shí)就覺(jué)得很好,舊文重讀,感覺(jué)理解得更多一些了,于是產(chǎn)生了翻譯出來(lái),與大家共享的想法。雖然文章不長(zhǎng),但時(shí)間有限,又若干次在翻譯時(shí)打盹睡著,拖拖拉拉用了小一個(gè)月。
一方面因本人水平所限,另一方面因翻譯時(shí)經(jīng)常打盹,錯(cuò)誤之處恐怕不少,歡迎大家批評(píng)指正。
前言 了解你所使用的編程語(yǔ)言究竟是如何實(shí)現(xiàn)的,對(duì)于C++程序員可能特別有意義。首先,它可以去除我們對(duì)于所使用語(yǔ)言的神秘感,使我們不至于對(duì)于編譯器干的活感到完全不可思議;尤其重要的是,它使我們?cè)贒ebug和使用語(yǔ)言高級(jí)特性的時(shí)候,有更多的把握。當(dāng)需要提高代碼效率的時(shí)候,這些知識(shí)也能夠很好地幫助我們。
本文著重回答這樣一些問(wèn)題:
* 類如何布局?
* 成員變量如何訪問(wèn)?
* 成員函數(shù)如何訪問(wèn)?
* 所謂的“調(diào)整塊”(adjuster thunk)是怎么回事?
* 使用如下機(jī)制時(shí),開(kāi)銷如何:
* 單繼承、多重繼承、虛繼承
* 虛函數(shù)調(diào)用
* 強(qiáng)制轉(zhuǎn)換到基類,或者強(qiáng)制轉(zhuǎn)換到虛基類
* 異常處理
首先,我們順次考察C兼容的結(jié)構(gòu)(struct)的布局,單繼承,多重繼承,以及虛繼承;接著,我們講成員變量和成員函數(shù)的訪問(wèn),當(dāng)然,這里面包含虛函數(shù)的情況;再接下來(lái),我們考察構(gòu)造函數(shù),析構(gòu)函數(shù),以及特殊的賦值操作符成員函數(shù)是如何工作的,數(shù)組是如何動(dòng)態(tài)構(gòu)造和銷毀的;最后,簡(jiǎn)單地介紹對(duì)異常處理的支持。
對(duì)每個(gè)語(yǔ)言特性,我們將簡(jiǎn)要介紹該特性背后的動(dòng)機(jī),該特性自身的語(yǔ)意(當(dāng)然,本文決不是“C++入門”,大家對(duì)此要有充分認(rèn)識(shí)),以及該特性在微軟的VC++中是如何實(shí)現(xiàn)的。這里要注意區(qū)分抽象的C++語(yǔ)言語(yǔ)意與其特定實(shí)現(xiàn)。微軟之外的其他C++廠商可能提供一個(gè)完全不同的實(shí)現(xiàn),我們偶爾也會(huì)將VC++的實(shí)現(xiàn)與其他實(shí)現(xiàn)進(jìn)行比較。
類布局 本節(jié)討論不同的繼承方式造成的不同
內(nèi)存布局。
1、C結(jié)構(gòu)(struct)
由于C++基于C,所以C++也“基本上”兼容C。特別地,C++規(guī)范在“結(jié)構(gòu)”上使用了和C相同的,簡(jiǎn)單的內(nèi)存布局原則:成員變量按其被聲明的順序排列,按具體實(shí)現(xiàn)所規(guī)定的對(duì)齊原則在內(nèi)存地址上對(duì)齊。所有的C/C++廠商都保證他們的C/C++編譯器對(duì)于有效的C結(jié)構(gòu)采用完全相同的布局。這里,A是一個(gè)簡(jiǎn)單的C結(jié)構(gòu),其成員布局和對(duì)齊方式都一目了然。

struct A { ?? char c; ?? int i; }; |
譯者注:從上圖可見(jiàn),A在內(nèi)存中占有8個(gè)字節(jié),按照聲明成員的順序,前4個(gè)字節(jié)包含一個(gè)字符(實(shí)際占用1個(gè)字節(jié),3個(gè)字節(jié)空著,補(bǔ)對(duì)齊),后4個(gè)字節(jié)包含一個(gè)整數(shù)。A的指針就指向字符開(kāi)始字節(jié)處。
2、有C++特征的C結(jié)構(gòu)
當(dāng)然了,C++不是復(fù)雜的C,C++本質(zhì)上是面向?qū)ο蟮恼Z(yǔ)言:包含繼承、封裝,以及多態(tài)。原始的C結(jié)構(gòu)經(jīng)過(guò)改造,成了面向?qū)ο笫澜绲幕悺3顺蓡T變量外,C++類還可以封裝成員函數(shù)和其他東西。然而,有趣的是,除非為了實(shí)現(xiàn)虛函數(shù)和虛繼承引入的隱藏成員變量外,C++類實(shí)例的大小完全取決于一個(gè)類及其基類的成員變量!成員函數(shù)基本上不影響類實(shí)例的大小。
這里提供的B是一個(gè)C結(jié)構(gòu),然而,該結(jié)構(gòu)有一些C++特征:控制成員可見(jiàn)性的“public/protected/private”關(guān)鍵字、成員函數(shù)、靜態(tài)成員,以及嵌套的類型聲明。雖然看著琳瑯滿目,實(shí)際上只有成員變量才占用類實(shí)例的空間。要注意的是,C++標(biāo)準(zhǔn)委員會(huì)不限制由“public/protected/private”關(guān)鍵字分開(kāi)的各段在實(shí)現(xiàn)時(shí)的先后順序,因此,不同的編譯器實(shí)現(xiàn)的內(nèi)存布局可能并不相同。(在VC++中,成員變量總是按照聲明時(shí)的順序排列)。

struct B { public: ?? int bm1; protected: ?? int bm2; private: ?? int bm3; ?? static int bsm; ?? void bf(); ?? static void bsf(); ?? typedef void* bpv; ?? struct N { }; }; |
譯者注:B中,為何static int bsm不占用內(nèi)存空間?因?yàn)樗庆o態(tài)成員,該數(shù)據(jù)存放在程序的數(shù)據(jù)段中,不在類實(shí)例中。
3、單繼承
C++提供繼承的目的是在不同的類型之間提取共性。比如,科學(xué)家對(duì)物種進(jìn)行分類,從而有種、屬、綱等說(shuō)法。有了這種層次結(jié)構(gòu),我們才可能將某些具備特定性質(zhì)的東西歸入到最合適的分類層次上,如“懷孩子的是哺乳動(dòng)物”。由于這些屬性可以被子類繼承,所以,我們只要知道“鯨魚、人”是哺乳動(dòng)物,就可以方便地指出“鯨魚、人都可以懷孩子”。那些特例,如鴨嘴獸(生蛋的哺乳動(dòng)物),則要求我們對(duì)缺省的屬性或行為進(jìn)行覆蓋。
C++中的繼承語(yǔ)法很簡(jiǎn)單,在子類后加上“:base”就可以了。下面的D繼承自基類C。

struct C { ?? int c1; ?? void cf(); }; |

struct D : C { ?? int d1; ?? void df(); }; |
既然派生類要保留基類的所有屬性和行為,自然地,每個(gè)派生類的實(shí)例都包含了一份完整的基類實(shí)例數(shù)據(jù)。在D中,并不是說(shuō)基類C的數(shù)據(jù)一定要放在D的數(shù)據(jù)之前,只不過(guò)這樣放的話,能夠保證D中的C對(duì)象地址,恰好是D對(duì)象地址的第一個(gè)字節(jié)。這種安排之下,有了派生類D的指針,要獲得基類C的指針,就不必要計(jì)算偏移量了。幾乎所有知名的C++廠商都采用這種內(nèi)存安排。在單繼承類層次下,每一個(gè)新的派生類都簡(jiǎn)單地把自己的成員變量添加到基類的成員變量之后。看看上圖,C對(duì)象指針和D對(duì)象指針指向同一地址。