1. 程式人生 > >C++ 工程實踐(4):二進位制相容性http://blog.csdn.net/Solstice/article/details/6233478

C++ 工程實踐(4):二進位制相容性http://blog.csdn.net/Solstice/article/details/6233478

陳碩 (giantchen_AT_gmail)

Blog.csdn.net/Solstice

本文主要討論 Linux x86/x86-64 平臺,偶爾會舉 Windows 作為反面教材。

C/C++ 的二進位制相容性 (binary compatibility) 有多重含義,本文主要在“標頭檔案和庫檔案分別升級,可執行檔案是否受影響”這個意義下討論,我稱之為 library (主要是 shared library,即動態連結庫)的 ABI (application binary interface)。至於編譯器與作業系統的 ABI 留給下一篇談 C++ 標準與實踐的文章。

什麼是二進位制相容性

在解釋這個定義之前,先看看 Unix/C 語言的一個歷史問題:open() 的 flags 引數的取值。open(2) 函式的原型是

int open(const char *pathname, int flags);

其中 flags 的取值有三個: O_RDONLY,  O_WRONLY,  O_RDWR。

與一般人的直覺相反,這幾個值不是按位或 (bitwise-OR) 的關係,即 O_RDONLY | O_WRONLY != O_RDWR。如果你想以讀寫方式開啟檔案,必須用 O_RDWR,而不能用 (O_RDONLY | O_WRONLY)。為什麼?因為 O_RDONLY, O_WRONLY, O_RDWR 的值分別是 0, 1, 2。它們不滿足按位或 。

那麼為什麼 C 語言從誕生到現在一直沒有糾正這個不足之處?比方說把 O_RDONLY, O_WRONLY, O_RDWR 分別定義為 1, 2, 3,這樣 O_RDONLY | O_WRONLY == O_RDWR,符合直覺。而且這三個值都是巨集定義,也不需要修改現有的原始碼,只需要改改系統的標頭檔案就行了。

因為這麼做會破壞二進位制相容性。對於已經編譯好的可執行檔案,它呼叫 open(2) 的引數是寫死的,更改標頭檔案並不能影響已經編譯好的可執行檔案。比方說這個可執行檔案會呼叫 open(path, 1) 來 檔案,而在新規定中,這表示 檔案,程式就錯亂了。

以上這個例子說明,如果以 shared library 方式提供函式庫,那麼標頭檔案和庫檔案不能輕易修改,否則容易破壞已有的二進位制可執行檔案,或者其他用到這個 shared library 的 library。作業系統的 system call 可以看成 Kernel 與 User space 的 interface,kernel 在這個意義下也可以當成 shared library,你可以把核心從 2.6.30 升級到 2.6.35,而不需要重新編譯所有使用者態的程式。

所謂“二進位制相容性”指的就是在升級(也可能是 bug fix)庫檔案的時候,不必重新編譯使用這個庫的可執行檔案或使用這個庫的其他庫檔案,程式的功能不被破壞。

在 Windows 下有惡名叫 DLL Hell,比如 MFC 有一堆 DLL,mfc40.dll, mfc42.dll, mfc71.dll, mfc80.dll, mfc90.dll,這是動態連結庫的本質問題,怪不到 MFC 頭上。

有哪些情況會破壞庫的 ABI

到底如何判斷一個改動是不是二進位制相容呢?這跟 C++ 的實現方式直接相關,雖然 C++ 標準沒有規定 C++ 的 ABI,但是幾乎所有主流平臺都有明文或事實上的 ABI 標準。比方說 ARM 有 EABI,Intel Itanium 有http://www.codesourcery.com/public/cxx-abi/abi.html ,x86-64 有仿 Itanium 的 ABI,SPARC 和 MIPS 也都有明文規定的 ABI,等等。x86 是個例外,它只有事實上的 ABI,比如 Windows 就是 Visual C++,Linux 是 G++(G++ 的 ABI 還有多個版本,目前最新的是 G++ 3.4 的版本),Intel 的 C++ 編譯器也得按照 Visual C++ 或 G++ 的 ABI 來生成程式碼,否則就不能與系統其它部件相容。

C++ ABI 的主要內容:

  • 函式引數傳遞的方式,比如 x86-64 用暫存器來傳函式的前 4 個整數引數
  • 虛擬函式的呼叫方式,通常是 vptr/vtbl 然後用 vtbl[offset] 來呼叫
  • struct 和 class 的記憶體佈局,通過偏移量來訪問資料成員
  • name mangling
  • RTTI 和異常處理的實現(以下本文不考慮異常處理)

C/C++ 通過標頭檔案暴露出動態庫的使用方法,這個“使用方法”主要是給編譯器看的,編譯器會據此生成二進位制程式碼,然後在執行的時候通過裝載器(loader)把可執行檔案和動態庫綁到一起。如何判斷一個改動是不是二進位制相容,主要就是看標頭檔案暴露的這份“使用說明”能否與新版本的動態庫的實際使用方法相容。因為新的庫必然有新的標頭檔案,但是現有的二進位制可執行檔案還是按舊的標頭檔案來呼叫動態庫。

這裡舉一些原始碼相容但是二進位制程式碼不相容例子

  • 給函式增加預設引數,現有的可執行檔案無法傳這個額外的引數。
  • 增加虛擬函式,會造成 vtbl 裡的排列變化。(不要考慮“只在末尾增加”這種取巧行為,因為你的 class 可能已被繼承。)
  • 增加預設模板型別引數,比方說 Foo 改為 Foo >,這會改變 name mangling
  • 改變 enum 的值,把 enum Color { Red = 3 }; 改為 Red = 4。這會造成錯位。當然,由於 enum 自動排列取值,新增 enum 項也是不安全的,除非是在末尾新增。

給 class Bar 增加資料成員,造成 sizeof(Bar) 變大,以及內部資料成員的 offset 變化,這是不是安全的?通常不是安全的,但也有例外。

  • 如果客戶程式碼裡有 new Bar,那麼肯定不安全,因為 new 的位元組數不夠裝下新 Bar。相反,如果 library 通過 factory 返回 Bar* (並通過 factory 來銷燬物件)或者直接返回 shared_ptr,客戶端不需要用到 sizeof(Bar),那麼可能是安全的。 同樣的道理,直接定義 Bar bar; 物件(無論是函式區域性物件還是作為其他 class 的成員)也有二進位制相容問題。
  • 如果客戶程式碼裡有 Bar* pBar; pBar->memberA = xx;,那麼肯定不安全,因為 memberA 的新 Bar 的偏移可能會變。相反,如果只通過成員函式來訪問物件的資料成員,客戶端不需要用到 data member 的 offsets,那麼可能是安全的。
  • 如果客戶呼叫 pBar->setMemberA(xx); 而 Bar::setMemberA() 是個 inline function,那麼肯定不安全,因為偏移量已經被 inline 到客戶的二進位制程式碼裡了。如果 setMemberA() 是 outline function,其實現位於 shared library 中,會隨著 Bar 的更新而更新,那麼可能是安全的。

那麼只使用 header-only 的庫檔案是不是安全呢?不一定。如果你的程式用了 boost 1.36.0,而你依賴的某個 library 在編譯的時候用的是 1.33.1,那麼你的程式和這個 library 就不能正常工作。因為 1.36.0 和 1.33.1 的 boost::function 的模板引數型別的個數不一樣,其中一個多了 allocator。

哪些做法多半是安全的

前面我說“不能輕易修改”,暗示有些改動多半是安全的,這裡有一份白名單,歡迎新增更多內容。

只要庫改動不影響現有的可執行檔案的二進位制程式碼的正確性,那麼就是安全的,我們可以先部署新的庫,讓現有的二進位制程式受益。

  • 增加新的 class
  • 增加 non-virtual 成員函式
  • 修改資料成員的名稱,因為生產的二進位制程式碼是按偏移量來訪問的,當然,這會造成原始碼級的不相容。
  • 還有很多,不一一列舉了。

歡迎補充

反面教材:COM

在 C++ 中以虛擬函式作為介面基本上就跟二進位制相容性說拜拜了。具體地說,以只包含虛擬函式的 class (稱為 interface class)作為程式庫的介面,這樣的介面是僵硬的,一旦釋出,無法修改。

比方說 M$ 的 COM,其 DirectX 和 MSXML 都以 COM 元件方式釋出,我們來看看它的帶版本介面 (versioned interfaces):

  • IDirect3D7, IDirect3D8, IDirect3D9, ID3D10*, ID3D11*
  • IXMLDOMDocument, IXMLDOMDocument2, IXMLDOMDocument3

話句話說,每次釋出新版本都引入新的 interface class,而不是在現有的 interface 上做擴充。這樣不能相容現有的程式碼,強迫客戶端程式碼也要改寫。

回過頭來看看 C 語言,C/Posix 這些年逐漸加入了很多新函式,同時,現有的程式碼不用修改也能執行得很好。如果要用這些新函式,直接用就行了,也基本不會修改已有的程式碼。相反,COM 裡邊要想用 IXMLDOMDocument3 的功能,就得把現有的程式碼從 IXMLDOMDocument 全部升級到 IXMLDOMDocument3,很諷刺吧。

tip:如果遇到鼓吹在 C++ 裡使用面向介面程式設計的人,可以拿二進位制相容性考考他。

解決辦法

採用靜態連結

這個是王道。在分散式系統這,採用靜態連結也帶來部署上的好處,只要把可執行檔案放到機器上就行執行,不用考慮它依賴的 libraries。目前 muduo 就是採用靜態連結。

通過動態庫的版本管理來控制相容性

這需要非常小心檢查每次改動的二進位制相容性並做好釋出計劃,比如 1.0.x 系列做到二進位制相容,1.1.x 系列做到二進位制相容,而 1.0.x 和 1.1.x 二進位制不相容。《程式設計師的自我修養》裡邊講過 .so 檔案的命名與二進位制相容性相關的話題,值得一讀。

用 pimpl 技法,編譯器防火牆

在標頭檔案中只暴露 non-virtual 介面,並且 class 的大小固定為 sizeof(Impl*),這樣可以隨意更新庫檔案而不影響可執行檔案。當然,這麼做有多了一道間接性,可能有一定的效能損失。見 Exceptional C++ 有關條款和 C++ Coding Standards 101.

Java 是如何應對的

Java 實際上把 C/C++ 的 linking 這一步驟推遲到 class loading 的時候來做。就不存在“不能增加虛擬函式”,“不能修改 data member” 等問題。在 Java 裡邊用面向 interface 程式設計遠比 C++ 更通用和自然,也沒有上面提到的“僵硬的介面”問題。

(待續)

相關推薦

C++ 工程實踐(4)二進位制相容性http://blog.csdn.net/Solstice/article/details/6233478

陳碩 (giantchen_AT_gmail) Blog.csdn.net/Solstice 本文主要討論 Linux x86/x86-64 平臺,偶爾會舉 Windows 作為反面教材。 C/C++ 的二進位制相容性 (binary compatibility) 有多重含義,本文主要在“標頭檔案和庫檔案分

聚合與組合關系 文章出處http://blog.csdn.net/liushuijinger/article/details/6994265

聚合 兩個 keyword 之間 strong view sta 部分 uml 大家都知道UML的類圖中一般包含五種關系即 關聯 聚合 組合 泛化 依賴 有些人可能會感覺組合跟聚合有點難區分 說難其實是相對其他幾種關系而言 實際上想分清這兩種關系一點也不

一致性hash 參考http://blog.csdn.net/cywosp/article/details/23397179/

相同 算法 tail 一個 得到 其他 ron strong 分布式 hash好壞的四個定義: 平衡性:平衡性是指哈希的結果能夠盡可能分布到所有的緩沖中去,這樣可以使得所有的緩沖空間都得到利用。 單調性:單調性是指如果已經有一些內容通過哈希分派到了相應的緩沖中,又有新的緩沖

轉載 JAVA spring ioc原理 原文地址http://blog.csdn.net/it_man/article/details/4402245

nbsp animal 很難 details 如何實現 拋出異常 感覺 註入 extend 最近,買了本Spring入門書:spring In Action 。大致瀏覽了下感覺還不錯。就是入門了點。Manning的書還是不錯的,我雖然不像哪些只看Manning書的人那樣專註

spring中的Ioc技術是怎樣實現解耦的 原文地址 http://blog.csdn.net/liang5603/article/details/52002994

ioc容器 可能 深入 修改 知識 動態 出現 工廠方法 邏輯 1. IoC理論的背景我們都知道,在采用面向對象方法設計的軟件系統中,它的底層實現都是由N個對象組成的,所有的對象通過彼此的合作,最終實現系統的業務邏輯。圖1:軟件系統中耦合的對象如果我們打開機械式手表的後蓋,

Linux串列埠程式設計教程(三)——串列埠程式設計詳(原始碼)解http://blog.csdn.net/u011192270/article/details/48174353 Linux下的串列埠程式設計(二)----(圖文並茂,講解深刻)http://blog.csdn.net/w28252

Linux串列埠程式設計教程(三)——串列埠程式設計詳(原始碼)解:http://blog.csdn.net/u011192270/article/details/48174353 Linux下的串列埠程式設計(二)----(圖文並茂,講解深刻)http://blog.csdn.ne

人臉識別之人臉對齊(三)--AAM演算法原文 http://blog.csdn.net/colourfulcloud/article/details/9774017 AAM(Active Appear

原文: http://blog.csdn.net/colourfulcloud/article/details/9774017 AAM(Active Appearance Model)主動外觀模型主要分為兩個階段,模型建立階段和模型匹配階段。其中模型建立階段包括了對訓練樣本分別建立形狀模型(

轉載自http://blog.csdn.net/hguisu/article/details/7418161 作者為真實的歸宿

轉載自:http://blog.csdn.net/hguisu/article/details/7418161 作者為:真實的歸宿 1.什麼是IO       Java中I/O操作主要是指使用Java進行輸入,

原作者題目mahout推薦相似度學習總結 原文章路徑http://blog.csdn.net/a674810893/article/details/44729671

原作者題目:mahout推薦相似度學習總結     原文章路徑:http://blog.csdn.net/a674810893/article/details/44729671 mahout的推薦主要是基於協同過濾,協同過濾是通過了解使用者與物品之間的關係,

轉載來源http://blog.csdn.net/nacey5201/article/details/8547772

轉載來源:http://blog.csdn.net/nacey5201/article/details/8547772 一、配置方式 在Spring2.0中除了以前的Singleton和Prototype外又加入了三個新的web作用域,分別為request、session

Android APIDemos 研讀之二android.graphics.Camera 源文地址 : http://blog.csdn.net/sharetop/article/details/5277655

        Android APIDemos 研讀之二:android.graphics.Camera      源文地址 : http:/

Mongodb 叢集加keyFile認證,Mongodb使用者管理(轉http://blog.csdn.net/wlzjsj/article/details/61421230)

介紹 自從遠古計繩結開始,資料庫的儲存就註定了今天的地位和多樣性,Nosql的出現更是解決了現有的關係型資料庫無法解決的一些難題,對高效能,靈活度,擴充套件性,海量資料的問題。隨之而出現的高速記憶體索引資料庫、列式儲存、影象儲存等等,這篇文章主要講的是mongodb文件型資料庫,mongodb目

引用部落格http://blog.csdn.net/u012230055/article/details/64125268

一 、SourceTree簡介 SourceTree 是 Windows 和Mac OS X 下免費的 Git 和 Hg 客戶端,擁有視覺化介面,容易上手操作。同時它也是Mercurial和Subversion版本控制系統工具。支援建立、提交、clone、push、pu

sqlite 資料型別 全面(轉自http://blog.csdn.net/jin868/article/details/5961263)

一般資料採用的固定的靜態資料型別,而SQLite採用的是動態資料型別,會根據存入值自動判斷。SQLite具有以下五種資料型別: 1.NULL:空值。 2.INTEGER:帶符號的整型,具體取決有存入數字的範圍大小。 3.REAL:浮點數字,儲存為8-byte IEEE浮

Notepad++外掛之ftp/sftp遠端編輯功能,以及各種外掛(轉http://blog.csdn.net/happy_wu/article/details/73302994)

Notepad++的ftp/sftp遠端編輯功能介紹:Notepad++是一個優秀的開源編輯器,最大特性就是外掛豐富,這裡分享一下我常用的實用外掛,其中一些是Npp預裝的。這些外掛主要是開發相關的,所以如果想需要其它外掛,可以瀏覽一下。這裡我主要寫的是FTP外掛。一般情況下我

eclipse 集成Maven(轉自:http://blog.csdn.net/wode_dream/article/details/38052639)

lin loser 說明 位置 到你 ide lan core fontsize 當自己越來越多的接觸到開源項目時,發現大多數的開源項目都是用maven來夠建的。並且在開發應用時,也越來越意識到maven的確會解決很多問題,如果你要了解maven,可以參考:Maven入門

laravel session使用 轉自http://blog.csdn.net/angle_hearts/article/details/53923782

com lar new get ssi name sym 存儲 angle use Symfony\Component\HttpFoundation\Session\Session;//存儲session$session = new Session;$session->

Annovar註釋說明【轉載自http://blog.csdn.net/u013816205/article/details/51262289】

probably scores hit bar package 文件轉換 命名 gre 下載 ANNOVAR是一個perl編寫的命令行工具,能在安裝了perl解釋器的多種操作系統上 執行。允許多種輸入文件格式,包括最常被使用的VCF格式。輸出文件也有多種格式,包括註釋過的V

數據庫主鍵生成方式 轉http://blog.csdn.net/w183705952/article/details/7102920

digg .com 表示 加鎖 -m 之間 sequence 權力 ont 1) assigned主鍵由外部程序負責生成,無需Hibernate參與。2) hilo通過hi/lo 算法實現的主鍵生成機制,需要額外的數據庫表保存主鍵生成歷史狀態。3) seqhilo與hilo

文件上傳的思考 (轉) http://blog.csdn.net/ncafei/article/details/53401961

內容 html csdn 大小寫 eva 最大 設備 改變 tar 文件上傳校驗 客戶端JavaScript校驗(一般只校驗後綴名) 一般都是在網頁上寫一段javascript腳本,校驗上傳文件的後綴名,有白名單形式也有黑名單形式。  判斷方式:在瀏覽加載文