SD-二進(jìn)制兼容

0.目錄

  1. 定義
  2. ABI和API
  3. 二進(jìn)制兼容的相關(guān)問題
  4. C++抽象類和Java的接口
  5. 總結(jié)
  6. 參考

1.定義

所謂二進(jìn)制兼容就是在做版本升級(jí)(也可能是Bug fix)庫(kù)文件的時(shí)候蒋川,不必要做重新編譯使用這個(gè)庫(kù)的可執(zhí)行文件或使用這個(gè)庫(kù)的其他庫(kù)文件,同時(shí)能保證程序功能不被破壞邮破。

先明確兩個(gè)概念:二進(jìn)制兼容源碼兼容

  • 二進(jìn)制兼容:升級(jí)庫(kù)文件時(shí)揍堕,不必重新編譯使用此庫(kù)的可執(zhí)行文件或其他庫(kù)文件,且程序的功能不被破壞
  • 源代碼兼容:升級(jí)庫(kù)文件時(shí)蜒滩,不必修改使用此庫(kù)的可執(zhí)行文件或其他庫(kù)文件的源代碼只需重新編譯應(yīng)用程序,即可使程序的功能不被破壞

2.ABI和API

應(yīng)用二進(jìn)制接口(application binary interface玫氢,縮寫為ABI)描述了應(yīng)用程序(或者其他類型)和操作系統(tǒng)之間或其他應(yīng)用程序的低級(jí)接口。ABI涵蓋了各種細(xì)節(jié)谜诫,如:數(shù)據(jù)類型的大小漾峡、布局和對(duì)齊;調(diào)用約定等喻旷。

在了解二進(jìn)制兼容和源碼兼容兩個(gè)定義以后生逸,我們?cè)倏磁c其類似且對(duì)應(yīng)的兩個(gè)概念:ABIAPIABI不同于API(應(yīng)用程序接口)且预,API定義了源代碼和庫(kù)之間的接口槽袄,因此同樣的代碼可以在支持這個(gè)API的任何系統(tǒng)中編譯,然而ABI允許編譯好的目標(biāo)代碼在使用兼容ABI的系統(tǒng)中無(wú)需改動(dòng)就能運(yùn)行锋谐。

舉個(gè)例子掰伸,在Qt和Java兩種跨平臺(tái)程序中,API像是Qt的接口怀估,Qt有著通用接口狮鸭,源代碼只需要在支持Qt的環(huán)境下編譯即可。ABI更像是Jvm多搀,只要支持Jvm的系統(tǒng)上歧蕉,都可以運(yùn)行已有的Java程序。

C++的ABI

ABI更像是一個(gè)產(chǎn)品的使用說明書康铭,同理C++的ABI就是如何使用C++生成可執(zhí)行程序的一張說明書惯退。編譯器會(huì)根據(jù)這個(gè)說明書,生成二進(jìn)制代碼从藤。C++的ABI在不同的編譯器下會(huì)略有不同催跪。

c++ ABI的部分內(nèi)容舉例:

  • 函數(shù)參數(shù)傳遞的方式,比如 x86-64 用寄存器來傳函數(shù)的前 4 個(gè)整數(shù)參數(shù)
  • 虛函數(shù)的調(diào)用方式夷野,通常是vptr/vtbl然后用vtbl[offset]來調(diào)用
  • structclass的內(nèi)存布局懊蒸,通過偏移量來訪問數(shù)據(jù)成員

綜上所述,如果可執(zhí)行程序通過以上說明書訪問動(dòng)態(tài)鏈接庫(kù)A悯搔,以及此庫(kù)的升級(jí)版本A+骑丸,若按此說明書上的方法,可以無(wú)痛的使用A和A+,那么我們就稱庫(kù)A的這次升級(jí)是二進(jìn)制兼容的通危。

3.二進(jìn)制兼容的相關(guān)問題

3.1 破壞二進(jìn)制兼容的幾種常見方式

  • 添加新的虛函數(shù)
    修改虛函數(shù)表內(nèi)的排列順序铸豁,即使把新增加的虛函數(shù)放到最后一個(gè),也可能會(huì)引起問題菊碟,如該類作為父類被其他類繼承等节芥;

  • 修改函數(shù)的參數(shù)列表
    由于C++支持同名函數(shù)重載,C++編譯時(shí)逆害,會(huì)對(duì)函數(shù)名字進(jìn)行name mangling藏古,如果修改了函數(shù)的參數(shù)列表,經(jīng)過C++編譯器編譯后忍燥,函數(shù)的名稱就變了拧晕,現(xiàn)有的可執(zhí)行文件無(wú)法傳這個(gè)額外的參數(shù);

  • 不導(dǎo)出或者移除一個(gè)導(dǎo)出類

  • 改變類的繼承

  • 改變虛函數(shù)聲明時(shí)的順序
    偏移量改變梅垄,導(dǎo)致調(diào)用失敗

  • 添加/刪除非靜態(tài)成員變量
    改變?cè)擃惖膶?duì)象的大小厂捞,類的內(nèi)存布局改變,偏移量也發(fā)生變化队丝,如:

pfoo = new Foo(); // 由于sizeof(Foo)發(fā)生了變化靡馁,分配的內(nèi)存可能不夠
pfo->member_variable; // 可能會(huì)出錯(cuò),偏移量變化
// 當(dāng)使用 inline setxxx(x)時(shí)机久,也可能會(huì)出錯(cuò)臭墨,因?yàn)閕nline函數(shù)可能已經(jīng)編譯進(jìn)使用該庫(kù)的程序代碼中。
  • 改變非靜態(tài)成員變量的聲明順序
    偏移量改變

  • 增加默認(rèn)模板類型參數(shù)

// 如:
template <typename T> class Grid {}; // old
template <typename t, typenameContainer=vector> class Grid{}; // new
  • 改變enum的值
enum Color { Red = 3}; // old
enum Color { Red = 4}; // new
// 這會(huì)造成錯(cuò)位膘盖。當(dāng)然胧弛,由于enum自動(dòng)排列取值,添加enum項(xiàng)也是不安全的侠畔,除非是在末尾添加结缚。

3.2 不會(huì)破壞二進(jìn)制兼容的幾種常見方式

  • 添加非虛函數(shù)(包括構(gòu)造函數(shù))
  • 添加新的類
  • 在已存在的枚舉類型中添加一個(gè)枚舉值
  • 添加新的靜態(tài)成員變量
  • 修改成員變量名稱(偏移量未改變)
  • 增減類的友元聲明

只要我們知道了程序是以什么方式訪問動(dòng)態(tài)庫(kù)的(C++的ABI),那么我們就很好判斷软棺,哪些操作會(huì)破壞二進(jìn)制兼容红竭。更多方式請(qǐng)參見Policies/Binary Compatibility Issues With C++

3.3 解決二進(jìn)制兼容問題的相關(guān)方法

編寫庫(kù)時(shí)最大的問題是,不能安全地添加數(shù)據(jù)成員喘落,因?yàn)檫@會(huì)改變每個(gè)包含類對(duì)象(包括子類)的類茵宪,結(jié)構(gòu)或數(shù)組的大小和布局。

1. 使用Bitflags即位域

//old
uint m1 : 1;
uint m2 : 3;
uint m3 : 1;
//new
uint m1 : 1;
uint m2 : 3;
uint m3 : 1;
uint m4 : 2; // new member without breaking binary compatibility.

不會(huì)破壞二進(jìn)制兼容性瘦棋。 但需要根據(jù)字節(jié)對(duì)其取整稀火,否則可能會(huì)因改變了整個(gè)類的大小導(dǎo)致sizeof()之類的方法出問題,使用最后一位可能會(huì)在某些編譯器上引起問題兽狭。

2. 使用靜態(tài)庫(kù)(當(dāng)然也隨之帶來一系列弊端)

3. D指針設(shè)計(jì)模式(PImpl機(jī)制)

4. COM理論
COM (Component object model) 組件對(duì)象模型是微軟提出的一個(gè)想法憾股,它其實(shí)是一個(gè)規(guī)范鹿蜀,并且是二進(jìn)制規(guī)范箕慧,也就是說只要遵循這個(gè)規(guī)范服球,任何語(yǔ)言、任何平臺(tái)都可以相互調(diào)用相應(yīng)組件颠焦。

COM涉及到幾個(gè)概念:

  1. class ID斩熊,可以是CLSID - class的GUID 或者 IID - interface的GUID。COM通過這個(gè)ID來保證快語(yǔ)言伐庭,因?yàn)榛旧纤姓Z(yǔ)言都可以處理GUID字符串粉渠;另外COM開發(fā)者可以通過GUID來獲取到準(zhǔn)確的對(duì)象結(jié)構(gòu)。
  2. coclass - component object class圾另,簡(jiǎn)單來說就是COM組件提供給使用者的接口類霸株,這些類其實(shí)都是都繼承 IUnkown接口的抽象類,里面都是純虛函數(shù)集乔。這個(gè)IUnknown包含三個(gè)方法:
    • AddRef - 增加對(duì)象引用計(jì)數(shù)
    • Release - 減少引用計(jì)數(shù)去件,如果計(jì)數(shù)為0,則銷毀
    • QueryInterface - 根據(jù)GUID來查到對(duì)象

COM組件還涉及到注冊(cè)表扰路,它可以注冊(cè)到操作系統(tǒng)的注冊(cè)表中尤溜,這樣就算當(dāng)前這個(gè)組件DLL物理位置與運(yùn)行文件不在同一個(gè)目錄,也可以加載并獲取DLL的導(dǎo)出對(duì)象或者函數(shù)汗唱。更多了解可以看 CodeProject - Introduction to COM - What It Is and How to Use It宫莱。

那為什么可以說COM能保證二進(jìn)制兼容呢?

其實(shí)通過上面兩個(gè)概念可以有點(diǎn)思緒哩罪,所謂二進(jìn)制兼容對(duì)于C++ 來說就是要保證第三方使用DLL提供的接口對(duì)象時(shí)授霸,保證內(nèi)存布局不會(huì)改變,或者說不會(huì)影響际插。對(duì)于C++來說绝葡,對(duì)象內(nèi)存布局的主要包括:

變量
虛函數(shù) - 每個(gè)實(shí)例都會(huì)有一個(gè)虛函數(shù)列表(包括基類的)
對(duì)于COM實(shí)現(xiàn)來說,因?yàn)槭峭ㄟ^GUID來獲取對(duì)象腹鹉,并且這些對(duì)象都是由接口來提供的實(shí)例化(抽象類不能創(chuàng)建實(shí)例藏畅,這些實(shí)例都是繼承的子類實(shí)現(xiàn)),就像 caller ----> coclass (interface) --create--> instance 這樣調(diào)用功咒。
由于 instance 是在COM組件類(DLL)實(shí)例化以及釋放愉阎,所以其內(nèi)存布局對(duì)于 caller 來說是沒有影響的。

4.C++抽象類和Java的接口

之前我一直認(rèn)為C++的抽象類就類似于Java的接口力奋,現(xiàn)在發(fā)現(xiàn)榜旦,如果把一個(gè)C++的抽象類作為動(dòng)態(tài)庫(kù)的接口發(fā)布,那將是毀滅的景殷。因?yàn)槟銦o(wú)法增加虛函數(shù)溅呢,無(wú)法增加成員變量澡屡,這使得這個(gè)接口變得非常的不友好。這也就是Java接口的優(yōu)勢(shì)所在咐旧。Java 實(shí)際上把 C/C++ 的 linking 這一步驟推遲到 class loading 的時(shí)候來做驶鹉,便不存在上述二進(jìn)制兼容的問題。

理解Java二進(jìn)制兼容的關(guān)鍵是要理解延遲綁定(Late Binding)铣墨。延遲綁定是指Java直到運(yùn)行時(shí)才檢查類室埋、域、方法的名稱伊约,而不象C/C++的編譯器那樣在編譯期間就清除了類姚淆、域、方法的名稱屡律,代之以偏移量數(shù)值——這是Java二進(jìn)制兼容得以發(fā)揮作用的關(guān)鍵腌逢。
由于采用了延遲綁定技術(shù), 方法超埋、域搏讶、類的名稱直到運(yùn)行時(shí)才解析,意味著只要域纳本、方法等的名稱(以及類型)一樣窍蓝,類的主體可以任意替換。

5.總結(jié)

  • 盡可能的不要使用虛函數(shù)作為接口
  • 使用pimpl

6.參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末繁成,一起剝皮案震驚了整個(gè)濱河市吓笙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌巾腕,老刑警劉巖面睛,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異尊搬,居然都是意外死亡叁鉴,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門佛寿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來幌墓,“玉大人,你說我怎么就攤上這事冀泻〕B拢” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵弹渔,是天一觀的道長(zhǎng)胳施。 經(jīng)常有香客問我,道長(zhǎng)肢专,這世上最難降的妖魔是什么舞肆? 我笑而不...
    開封第一講書人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任焦辅,我火速辦了婚禮,結(jié)果婚禮上椿胯,老公的妹妹穿的比我還像新娘筷登。我一直安慰自己,他們只是感情好压状,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開白布仆抵。 她就那樣靜靜地躺著跟继,像睡著了一般种冬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舔糖,一...
    開封第一講書人閱讀 51,521評(píng)論 1 304
  • 那天娱两,我揣著相機(jī)與錄音,去河邊找鬼金吗。 笑死十兢,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的摇庙。 我是一名探鬼主播旱物,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼卫袒!你這毒婦竟也來了宵呛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤夕凝,失蹤者是張志新(化名)和其女友劉穎宝穗,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體码秉,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡逮矛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了转砖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片须鼎。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖府蔗,靈堂內(nèi)的尸體忽然破棺而出晋控,到底是詐尸還是另有隱情,我是刑警寧澤礁竞,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布糖荒,位于F島的核電站,受9級(jí)特大地震影響模捂,放射性物質(zhì)發(fā)生泄漏捶朵。R本人自食惡果不足惜蜘矢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望综看。 院中可真熱鬧品腹,春花似錦、人聲如沸红碑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)析珊。三九已至羡鸥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間忠寻,已是汗流浹背惧浴。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留奕剃,地道東北人衷旅。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像纵朋,于是被迫代替她去往敵國(guó)和親柿顶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355