標籤:

為什麼 C++ 標準不明確二進位介面 (ABI) 標準?

這麼明顯的問題卻不考慮,有什麼隱情嗎?


C其實也沒有標準ABI。你把不同機器的庫混用,理論上還是undefined behavior,即使這個庫不涉及系統調用。

C有的實際上是同一操作系統內的統一ABI,而這種待遇是C的地位決定的。(操作系統多由C或者C++寫成,多數實用語言都有辦法與C介面對接)

是操作系統定義了用戶C程call系統調用時的介面規範,導致這個規範成為事實上的該操作系統內的「標準」ABI。

在這裡操作系統是強勢方,C Compiler要是特立獨行硬要用自己的介面規範,編譯出來的程序只能自己和自己玩。

而操作系統沒有定義對其他語言的介面規範,又沒有其他人能提出一個有力服眾的介面規範,因此導致其他語言沒有"標準"ABI。與操作系統的交互通過extern "C"或者native介面之類的方法生成一個符合C ABI的介面來與操作系統對接。

總結一下:

是C的超然地位導致了OS定義對C的介面規範,而其他語言無此地位讓OS專門定義一個介面規範。即使是這個規範,也不是由C標準定義的,而是OS定義的。


ABI不是語言能定下來的,得看硬體答應不答應。不同CPU寄存器數量都不一樣,你規定前四個參數用寄存器傳遞,萬一人家空閑的寄存器只有三個怎麼辦?你規定64位以下的參數可以用寄存器傳參,人家寄存器是32位的怎麼辦?16位的呢?你規定參數按順序入棧,不對齊位元組邊界,萬一人家寄存器不對齊位元組邊界就不能讀取怎麼辦?

再比如,像x86這樣的雖然叫通用寄存器,但不同的寄存器功能是略有區別的,SP就是專用棧指針,可以配合push之類的指令,不能隨便拿來存別的;這樣,已有的寄存器如何分配,哪些通用,哪些保證函數調用時不變,哪些不能保證,這些就都需要按照硬體的特性去設計。所以不可能在所有的硬體上都保持相同的ABI。

雖然理論上來說相同的硬體可以用相同的ABI,但還得考慮到操作系統廠商(以及編譯器廠商)誰也不服誰的問題,這樣不同操作系統的ABI又可能會略有區別。


七八十年代很可能同一家公司不同型號電腦指令集、機器字長度都不一樣

現在司空見慣的「內存按位元組定址」是 80 年代才逐漸流行起來的


ABI這事其實跟語言無關,跟實現有關。

為什麼C有統一的ABI呢?主要是因為各主流的OS都以C作為系統API介面,所以在OS層面確定了在同一系統內C的ABI。

所以,C的ABI兼容性,其實是由OS來制定並且推廣的。對於不同風格的OS(例如說win-&>*nix),其實一樣是不兼容的。

而C++,沒有明確的ABI兼容性,主要就是缺少了一個強有力的推進機構。


ABI不是語言規範(比如這個C++14規範的草稿:http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4527.pdf)制定的,而是操作系統的ABI文檔制定的,比如這個SystemV AMD64 ABI:http://www.x86-64.org/documentation/abi.pdf

解釋一下為什麼ABI只規定C的ABI。引用SystemV AMD64 ABI的原文:

No attempt has been made to specify an ABI for languages other than C. However, it is assumed that many programming languages will wish to link with code written in C, so that the ABI specifications documented here apply there too.

簡單翻譯一下就是:

我們壓根兒不打算規定除了C語言以外的語言的ABI。我們默認許多其他的語言也會希望能和C語言共同編譯工作,因而這一C語言的ABI規範也適用於那些語言。


ABI 一般是由 (編譯器 	imes 操作系統 	imes 硬體架構) 這個組合制定的。


你說的 C++ ABI 具體指什麼?

對象布局?(單繼承、多繼承、虛繼承)

虛函數的實現機制?

name mangling 的規則?

異常的實現機制?

函數調用約定?

先把範圍定義清楚,再來討論標準化。


C++又沒有定義虛擬機(硬體+操作系統),何德何能可以定義自己的ABI啊...

其次,內部的很多實現也是誰都不服氣誰。

例如指向成員函數的指針,VC覺得實際使用絕大多數類都是單繼承(除開空基類),所以盡量優化他在單繼承的性能,完全如普通函數指針。但是虛函數 多繼承 虛繼承 的時候 一個比一個更複雜(用了各種thunk)。而且對未完全定義的類的處理會有麻煩。

而GCC領頭的派系卻完全不這樣,用了一套通用的方法把全部問題解決完,不會有thunk來處理特殊情況。。但是代價是在單繼承時,成員函數指針也會比VC要大一倍,執行的時候還需要判定一把是否虛函數,還要執行一把this指針調整。導致調用處代碼量也多了很多。

你能說誰更好,誰更配用來做標準嗎?其實很難說吧。反正我自己用的時候,都默默寫個static 成員函數調用非static成員函數,然後用static 成員函數的地址而不直接使用成員函數指針。


據說是標準委員會聲稱,ABI自由實現可以提高運行效率。但這怎麼看都是借口。真正的原因恐怕是各家的ABI下都積累了大量binary,已經統一不了了。

更何況C++的ABI複雜得要死。


其實吧,每個操作系統也都有該操作系統下的標準 C++ ABI 的,比如 Linux 和 macOS 用的 gcc 和 clang 都遵循 Itanium ABI,而微軟則是自己的一套 ABI。而更重要的問題是,每一次 C++ 標準的更新或多或少會引入一些現有 ABI 里沒有或者需要修改的東西,標準庫里的一些函數和模板的原型也有變化導致 mangle 成不同的符號,於是你只要用了新標準編譯的庫就無法跟舊程序兼容,或者你引用了標準庫里的新符號,在舊的庫里沒有。於是要實現新舊系統同時兼容,你就必須只能以舊的標準去寫程序,用舊版本編譯,鏈接舊版本的 C++ 標準庫。因為新的一般還是兼容舊的,像 libstdc++ 里有各個不同版本的符號。

這也是沒辦法的事,如果限制 ABI 不能變,那標準可以發揮的空間就太少了,在加新語言特性的時候要考慮太多東西,限制就太多了。

而對我來說,學會了 C++14 之後,感覺沒有 C++14 簡直是無法忍受的,甚至在公司升級 CentOS 7 之前,我寧願用 C,再加上同事也更 prefer C API,於是我乾脆又全部改成 C 了……


以C/C++這樣的系統來說,源代碼級的可移植性是終極的,保證這個就可以了。二進位目標代碼的可移植性如果有固然很方便,但是話說回來這也意味著此預編譯的二進位代碼往往不能利用新的處理器能力,反而是欠優的結果


為了做IPO還有人把所有文件include到一起編譯呢,統一了ABI還讓人怎麼愉快的玩耍……


即便不好完全統一,統一了大部分也是好事。

恐怕不是不想統一或者不統一有什麼好處,而是覺悟太晚,各家的abi都已經大行天下、相對成熟穩定了,想搞也搞不成了。


ABI一旦設計的前瞻性不足,就會限制語言的表達能力(也可能降低效率)。舉個最簡單的例子,C語言的符號命名規則決定了它不能做函數重載(只能知道符號名稱,卻不能看出方法的簽名,甚至返回值的類型都是不確定的)。C++採用了name mangling,才以醜陋的方式繞開這種限制。Java當年由於指令集設計缺陷,至今做不出真正的泛型。函數式語言社區也有類似的現象,scalac和GHC已經不知道多少個版本的ABI了。當初C++14時代就有人提出module的設想,沒成功估計也和ABI有一定關聯。

不過從實戰中看,大家已經總結了一些繞開這一缺陷的辦法,典型的就是pimpl手法。也有一些事實上大家幾乎都遵守的設計方法,比如Itanium ABI


應用範圍太廣,定不下來。

標準大多是規範,什麼叫規範,就是大家都自己搞了一套,區別又不大,花樣太多,統一一下。

為什麼有多套標準,因為差異太大,只能統一成幾個,統一不成一個,各自立一個標準吧。

c++的abi屬於花樣多的大家誰也不想改,gnu自己就有4個不兼容的abi,不要說還有ms/intel,關鍵C++還要兼容C,C那邊還有一堆嵌入式cpu。


linux好像有,如果要過操作系統認證的話,我記得好象是系統調用


實際上ABI變動很慢的,幾大平台/OS/編譯器廠商基本上說了算。


name mangling 淚流滿面


推薦閱讀:

關於Qt性能的損失,有沒有一個可以量化的概念?
Qt 框架哪些方面效率高,哪些方面效率低?
C++單元測試如何實施?
各位都是怎麼進行單元測試的?
MFC 過時了嗎?

TAG:C | 編譯器 |