《深度探索c++對象模型》(三)

前述

本章的的主題是Data語意學(xué)呻拌,主要是探究編譯器對class中的Data member的綁定、布局和存儲等操作亚茬,最后探究Data member存取和多種繼承方式之間的效率關(guān)系铸题,以及指向Data member的指針的效率問題。


參考書籍及鏈接:《深度探索c++對象模型》


0翰萨、本章基礎(chǔ)

1. 空類對象的大小是多少脏答?

class X { };//空類

對于空類,它有一個隱藏的1byte大小亩鬼,那個被編譯器安插進(jìn)去的一個char,這使得這一class的兩個objects得以在內(nèi)存中配置獨(dú)一無二的地址殖告。

2. class object的size會受到哪些因素的影響?

會影響class object的size的因素有如下三個雳锋,編譯器:

    1. 語言本身所造成的額外負(fù)擔(dān):當(dāng)語言支持virtual base classes時丛肮,就會導(dǎo)致一些額外負(fù)擔(dān)。需要一個指針魄缚,它或者指向virtual base class subobject,或者指向一個相關(guān)的表格宝与,表格用于存儲subobject地址或偏移值。
    1. 編譯器對于特殊情況所提供的優(yōu)化處理:Virtual base class subobject的1 byte大小也會出現(xiàn)在derived class上冶匹。
    1. Alignment(邊界對齊)的限制:在大部分的機(jī)器上习劫,聚合的結(jié)構(gòu)體大小會受到alignment的限制,使他們能夠更有效率地在內(nèi)存中被存取嚼隘。比如32機(jī)器字上就是4的整數(shù)倍诽里。

3. 各種類型data member的存放。

nonstatic直接放在class object之中飞蛹。static data member放置在程序的一個global data segment中,不會影響個別class object的大小席镀。無論class產(chǎn)生多少個object,甚至是0個祭犯,其static data members永遠(yuǎn)也只存在一份實(shí)例。但是一個template classs的static data members的行為稍有不同焰宣。

一、Data member的綁定

1. member function取用的是global還是local data member?

當(dāng)member funtion取用Data時捕仔,優(yōu)先考慮member data,人們稱這種情況為“member rewriting rule”匕积,意思是對于member functions本身的分析,會直到整個class的聲明都出現(xiàn)了才開始榜跌。在一個inline member function軀體之內(nèi)的一個data member綁定操作闪唆,會在整個class聲明之后才發(fā)生。

以前人們提倡兩種程序設(shè)計(jì)風(fēng)格钓葫,即將所有的data members放在class聲明起始處悄蕾,或者把所有的inline function都放在class聲明之外。就是為解決綁定問題础浮,但這種情況在c++ 2.0之后已經(jīng)解決了帆调。

2. member function的argument list的情況又是怎么樣的呢?

與取用data member不同的是,argument list中的名稱還是會在它們第一次 遭遇時被適當(dāng)?shù)貨Q議(resolved)完成霸旗。

typedef int length;

class Point3d{
public:
    void mumble(length val) { _val=val;} //length被決議為global
    length mumble() {return val;}
    // ...
private:
    typedef float length;//這樣的聲明將使先前的參考操作不合法
    length _val;
    // ...
};

雖然編譯器能處理贷帮,但還是提倡一種防御性程序風(fēng)格:即總是把“nested(嵌套的) type聲明”放在class的起始處。

二诱告、Data member的布局

1. Data member是怎樣被放置的撵枢?

關(guān)于data member的布局,記住以下三點(diǎn):

  • nonstatic data members在class object中的排列順序和其被聲明的順序一樣精居,任何中間介入的static data members都不會被放進(jìn)對象布局之中锄禽。
  • C++ standard允許編譯器將多個access sections(也就是private、public靴姿、protected等區(qū)段)之中的data members整體自由排列沃但,不必在乎他們的出現(xiàn)在class中的聲明順序(連續(xù)的兩個privata也算兩個section)。
  • 編譯器還可能會合成一些內(nèi)部使用的data members佛吓,以支持整個對象模型宵晚,vptr就是這樣的東西,當(dāng)前所有的編譯器都把它安插在每一個“內(nèi)含virtual function之class”的object內(nèi)维雇。

三淤刃、Data member的存取

1. 經(jīng)由一個class object和一個指針存取data member,有重大差異嗎吱型?

答案是顯然的逸贾,這跟data member的類型和class的繼承等都有關(guān)系,分如下兩種情況討論:

  • data member 為 static
    static data members會被編譯器提出于class之外,并被視為一個global變量(但只在class生命范圍內(nèi)可見)铝侵。每一個static data member只有一個實(shí)例灼伤,存放在程序的data segment之中,通過一個指針和通過一個對象來存取data member都是一樣的咪鲜。

    若取一個static data member的地址狐赡,會得到一個指向其數(shù)據(jù)類型的指針,而不是一個指向其class member的指針嗜诀,因?yàn)閟tatic member并不內(nèi)含在一個class object之中猾警。

    如果有兩個classes孔祸,每一個都聲明了一個同名的static member隆敢,編譯器就會暗中對每一個static data member編碼(對于這種手法有個很美的名稱:name-mangling),以獲得一個獨(dú)一無的程序識別代碼崔慧。

  • data member 為 nonstatic
    Nonstatic data members直接存放在每一個class object之中拂蝎。只有經(jīng)過class object才能存取它們(implicit 存取如this指針)。欲對一個nonstatic data member進(jìn)行存取操作惶室,編譯器需要把class object的起始地址加上data member的偏移位置(offset)温自。

    每一個nonstatic data member的偏移位置(offset)在編譯時期即可獲知,甚至如果member屬于一個base class subobject(派生自單一或多重繼承串鏈)也是一樣的皇钞。因此悼泌,存取一個nonstatic data member,其效率和存取一個C struct member或一個nonderived class的member是一樣的夹界。

    但是如果該data member是一個virtual base class 的member,那么通過指針的存取速度會稍慢一點(diǎn)馆里。(指針的真正class type 只有在執(zhí)行器才真正確定)。

四可柿、“繼承”與Data Member

C++ standard未強(qiáng)制指定derived class members和base class members的排列順序鸠踪,理論上編譯器可以自由安排之。在大部分編譯器上頭复斥,base class members總是先出現(xiàn)营密,但屬于virtual base class的除外∧慷В“繼承”會對Data Member的布局有什么影響评汰?接下來分四種情況進(jìn)行討論。

1. 第一種情況:只要繼承不要多態(tài)痢虹。

這種情況不會存儲時間上的額外負(fù)擔(dān)被去,由于base class和derived class的objects都是從相同的地址開始,其差異只在于derived object 比較大世分,用以容納自建的nonstatic data members编振,把一個derived class object指定給base class 的指針或引用,并不需要編譯器去調(diào)停或修改地址踪央,可以提供了最佳執(zhí)行效率臀玄。

2. 第二種情況:加上多態(tài)。

加上virtual function接口后畅蹂,彈性增加了健无,但也同時增加了空間和存取時間上的額外負(fù)擔(dān),如何取舍液斜,視多態(tài)程序所帶來的利益累贤。可能帶來的額外負(fù)擔(dān)如下:

  • 導(dǎo)入一個和virtual table 少漆,用來存儲它所聲明的每一個virtual functions的地址臼膏。再加上一兩個slots(type_info)。
  • 在每一個class object中導(dǎo)入一個vptr,提供執(zhí)行期的鏈接示损,使每一個object能夠找到相應(yīng)的virtual table渗磅。
  • 加強(qiáng)constructor,使它能夠?yàn)関ptr設(shè)定初始值检访,讓它指向class所對應(yīng)的virtual table始鱼。
  • 加強(qiáng)destructor,使它能夠消抹“指向class 相關(guān)virtual table”的vptr脆贵。

3. 第三種情況:多重繼承医清。

對于單一繼承,如果沒有virtual function卖氨,那么編譯器就不需要做其他工作;但如果base class沒有virtual function而derived class有会烙,并且vptr放在object首部,那么當(dāng)把一個derived object轉(zhuǎn)換為其base object時双泪,就需要編譯器對vptr進(jìn)行調(diào)整持搜。在既是多重繼承又是虛擬繼承的情況下,編譯器的需要做的會更多焙矛。
對一個多重派生對象葫盼,將其地址指定給“最左端(也就是第一個)base class的指針”,情況將和單一繼承時相同村斟,因?yàn)槎叨贾赶蛳嗤钠鹗嫉刂菲兜肌V劣诘诙€或后繼的base class的地址指定操作,則需要將地址修改為:加上(或減去)介于中間的base class subobjects大小蟆盹。比較需要注意的是孩灯,如果在取drived class object的地址時進(jìn)行偏移計(jì)算時,若其為指針逾滥,就需要判斷其是否為0峰档,若為0則基類object的地址也應(yīng)為0。當(dāng)然,這些都是編譯器的工作讥巡,我們需要了解掀亩,但不需要自己去實(shí)現(xiàn)。

如果要存取第二個(或后繼)base class中的一個data member會是怎樣的情況欢顷?需要付出額外的成本嗎槽棍? 不,members的位置在編譯期就固定了抬驴,因此炼七,存取members只是一個簡單的offset運(yùn)算,就像單一繼承一樣簡單布持,不管是經(jīng)由一個指針豌拙,一個reference或是一個object來存取。

4. 第四種情況:虛擬繼承鳖链。

虛擬繼承的出現(xiàn)是為了避免多個相同base class subobject的出現(xiàn)姆蘸,將其只保留一份墩莫,從而減少空間浪費(fèi)芙委。
class如果含有一個或多個virtual base class subobjects將被分割為兩部分:一個不變區(qū)域和一個共享區(qū)域。不變區(qū)域中的數(shù)據(jù)狂秦,總是能有固定的offset灌侣,這部分可以被直接存取,至于共享部分裂问,所表現(xiàn)的就是virtual base class subobject 侧啼,這個部分?jǐn)?shù)據(jù),其位置因?yàn)槊看闻缮僮鞫凶兓安荆灾荒荛g接存取痊乾。

一般而言,virtual base class最有效的一種運(yùn)用形式就是:一個抽象的virtual base class椭更,沒有任何data members哪审。

五、對象成員的效率

程序員如果只關(guān)心起程序效率虑瀑,應(yīng)該實(shí)際測試湿滓,不能光憑推論、常識判斷或假設(shè)舌狗。
參考書籍作者所做的測試表明叽奥,虛擬繼承所造成確實(shí)會嚴(yán)重影響data member的存取效率。

五痛侍、指向Data members的指針(Pointer to Data Members)

1. 如果獲取Data member的偏移值朝氓?偏移值應(yīng)該為多少?

通過如(&Point3d::z)這樣的操作可以獲得data member的偏移值。實(shí)際測試表明所獲得的offset比預(yù)想大1赵哲,這是為什么嘹狞?實(shí)際上這樣做的目的是為了區(qū)分一個“沒有指向任何data member”的指針,和一個指向“第一個data member”的指針的情況誓竿。比如:

float Point3d::*p1 = 0;//“沒有指向任何data member”的指針
float Point3d::*p2 = &Point3d::x;//指向“第一個data member”的指針

if(p1 == p2) //如何區(qū)分?
{
    cout << "p1 & p2 contain the same value --" ;
    cout << " they must address the same member!" << endl;
}

因此磅网,不論編譯器或使用者都必須記住,在真正使用該值以指出一個member之前筷屡,請先減掉1涧偷。

2.“指向Member的指針”對數(shù)據(jù)的存取有什么影響?

無繼承時毙死,指向member的指針對數(shù)據(jù)的存取操作燎潮,首先需要計(jì)算offset-1,其次具體的object需要用offset計(jì)算地址,會極大地降低效率扼倘,但目前的一些編譯器提供了對應(yīng)的優(yōu)化确封,可以使其像直接通過對象取值一下快速。
有繼承時再菊,data member是直接放在class object中的爪喘,理論上不會影響代碼的效率,但繼承的使用會妨礙優(yōu)化的效果纠拔。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末秉剑,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子稠诲,更是在濱河造成了極大的恐慌侦鹏,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件臀叙,死亡現(xiàn)場離奇詭異略水,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)劝萤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進(jìn)店門渊涝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人稳其,你說我怎么就攤上這事驶赏。” “怎么了既鞠?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵煤傍,是天一觀的道長。 經(jīng)常有香客問我嘱蛋,道長蚯姆,這世上最難降的妖魔是什么五续? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮龄恋,結(jié)果婚禮上疙驾,老公的妹妹穿的比我還像新娘。我一直安慰自己郭毕,他們只是感情好它碎,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著显押,像睡著了一般扳肛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上乘碑,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天挖息,我揣著相機(jī)與錄音,去河邊找鬼兽肤。 笑死套腹,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的资铡。 我是一名探鬼主播电禀,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼害驹!你這毒婦竟也來了鞭呕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤宛官,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后瓦糕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體底洗,經(jīng)...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年咕娄,在試婚紗的時候發(fā)現(xiàn)自己被綠了亥揖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,769評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡圣勒,死狀恐怖费变,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情圣贸,我是刑警寧澤挚歧,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站吁峻,受9級特大地震影響滑负,放射性物質(zhì)發(fā)生泄漏在张。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一矮慕、第九天 我趴在偏房一處隱蔽的房頂上張望帮匾。 院中可真熱鬧,春花似錦痴鳄、人聲如沸瘟斜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哼转。三九已至,卻和暖如春槽华,著一層夾襖步出監(jiān)牢的瞬間壹蔓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工猫态, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留佣蓉,地道東北人。 一個月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓亲雪,卻偏偏與公主長得像勇凭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子义辕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 前述 本章的的主題是構(gòu)造函數(shù)語意學(xué)虾标,主要是挖掘編譯器對于“對象構(gòu)造過程”的干涉,以及對于“程序形式”和“程序效率”...
    野渡渡閱讀 579評論 0 0
  • 前述 看完《深度探索c++對象模型》灌砖,心中對c++編譯器在編譯期間所做的處理有了更深入的認(rèn)識璧函,我想,除了對編譯器本...
    野渡渡閱讀 2,387評論 0 1
  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom閱讀 2,701評論 0 3
  • 剛來簡書基显。 從前也是一個默默的孤獨(dú)的碼字人蘸吓。 高中寫了一整本日記,和現(xiàn)在的老公撩幽,高中的男友堅(jiān)持寫信库继,為了表達(dá)的更深...
    大園子ZXY閱讀 193評論 0 0
  • 月考成績優(yōu)異者 單科狀元 優(yōu)秀的班干部隊(duì)伍 進(jìn)步之星 班會課上,小小主持人 踴躍回答問題的我窜醉,我們 晚自習(xí)的我們 ...
    msf_a938閱讀 346評論 0 0