0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀(guān)看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

“C不再是一種編程語(yǔ)言”

GReq_mcu168 ? 來(lái)源:gankra ? 作者:Aria Beingessner ? 2022-05-05 14:43 ? 次閱讀

導(dǎo)讀:本文標(biāo)題里的觀(guān)點(diǎn)很“刺激”,它來(lái)自國(guó)外一位 Swift 和 Rust 專(zhuān)家 Aria Beingessner,他近日撰寫(xiě)了一篇文章《C 不再是一種編程語(yǔ)言》,在技術(shù)社區(qū)引起了熱議。

Beingessner 和他的朋友 Phantomderp 發(fā)現(xiàn)彼此在 C語(yǔ)言的某個(gè)方面都有著高度一致的意見(jiàn)——對(duì) C ABI 感到憤怒,并試圖修復(fù)它們。盡管他們各自憤怒的原因不盡相同,但本文作者想要表達(dá)的是:“C 被提升到了一個(gè)具備聲望和權(quán)威的角色,它的統(tǒng)治是如此地絕對(duì)和永恒,以至于它完全扭曲了我們之間的對(duì)話(huà)方式?!?/strong>“Rust 和 Swift 不能簡(jiǎn)單地‘說(shuō)’自己的母語(yǔ)或舒適的語(yǔ)言——它們必須怪異地模擬 C 的皮膚,并把自己包裹其中,使肉體以同樣的方式起伏?!?/p>

比喻雖尖銳,依據(jù)卻不無(wú)道理。幾乎任何程序要做任何有用或有趣的事情,它都必須在操作系統(tǒng)上運(yùn)行。這意味著它必須與那個(gè)操作系統(tǒng)交互——而很多操作系統(tǒng)都是用 C 編寫(xiě)的。因此,該語(yǔ)言必須與 C 代碼交互,這意味著它必須調(diào)用 C API。這是通過(guò)外部功能接口(FFI)完成的。換句話(huà)說(shuō),即使你從未用 C 編寫(xiě)任何代碼,你也必須處理 C 變量、匹配 C 數(shù)據(jù)結(jié)構(gòu)和布局、通過(guò)名稱(chēng)和符號(hào)鏈接到 C 函數(shù)。這不僅適用于任何語(yǔ)言與操作系統(tǒng)的交互,也適用于從一種語(yǔ)言調(diào)用另一種語(yǔ)言。

雖然很多人都表示自己喜歡 C,但對(duì)文章的內(nèi)容也是表達(dá)了認(rèn)可和贊同。

更精確地說(shuō),這篇文章的核心并不是“C 不再是編程語(yǔ)言”,而是“C 不僅僅是一種編程語(yǔ)言”。InfoQ 對(duì)原文進(jìn)行了翻譯,以饗讀者。以下內(nèi)容節(jié)選自原文:

C 是編程通用語(yǔ)言,我們都必須學(xué) C,因此 C 不再只是一種編程語(yǔ)言,它成了每一種通用編程語(yǔ)言都需要遵守的協(xié)議。

本文僅探討“C 由實(shí)現(xiàn)定義導(dǎo)致的難以捉摸的混亂”,這個(gè)讓所有人都不得不使用的協(xié)議已經(jīng)變成了一個(gè)更大的噩夢(mèng)。

外部函數(shù)接口

首先,讓我們從技術(shù)的角度看看。你完成了新語(yǔ)言 Bappyscript 的設(shè)計(jì),它對(duì) Bappy Paws/Hooves/Fins 提供了一流的支持。這是一門(mén)神奇的語(yǔ)言,它將徹底改變?nèi)藗兊木幊谭绞剑?/p>

但現(xiàn)在,你需要用它做一些有用的事情,比如,接受用戶(hù)的輸入,或者輸出結(jié)果,或者任何可見(jiàn)的東西。如果你希望用你的語(yǔ)言編寫(xiě)的程序成為優(yōu)秀的公民,可以在主要的操作系統(tǒng)上很好地運(yùn)行,那么你就需要與操作系統(tǒng)接口進(jìn)行交互。我聽(tīng)說(shuō),Linux 上的任何東西都“只是一個(gè)文件”,所以讓我們?cè)?Linux 上打開(kāi)一個(gè)文件。

OPEN(2)
NAME       open, openat, creat - open and possibly create a file
SYNOPSIS
       #include 
       int open(const char *pathname, int flags);       int open(const char *pathname, int flags, mode_t mode);
       int creat(const char *pathname, mode_t mode);
       int openat(int dirfd, const char *pathname, int flags);       int openat(int dirfd, const char *pathname, int flags, mode_t mode);
       /* Documented separately, in openat2(2): */       int openat2(int dirfd, const char *pathname,                   const struct open_how *how, size_t size);
   Feature Test Macro Requirements for glibc (see   feature_test_macros(7)):
       openat():           Since glibc 2.10:               _POSIX_C_SOURCE >= 200809L           Before glibc 2.10:_ATFILE_SOURCE

對(duì)不起,什么?這是 Bappyscript,不是 C。那 Linux 的 Bappyscript 接口在哪里?

你說(shuō) Linux 沒(méi)有 Bappyscript 接口是什么意思???好吧,這是一種全新的語(yǔ)言,但你會(huì)添加一個(gè),對(duì)吧?這時(shí)候你會(huì)想,我們好像必須使用他們給的東西。

我們將需要某種接口,使我們的語(yǔ)言能夠調(diào)用外部函數(shù)。外部函數(shù)接口,是的,F(xiàn)FI......然后你發(fā)現(xiàn),什么,Rust,你也有 C 的 FFI?Swift 你也有嗎?甚至連 Python 也有?!

9209af5a-cc3b-11ec-bce3-dac502259ad0.png

為了與主要的操作系統(tǒng)對(duì)話(huà),每種語(yǔ)言都必須學(xué)會(huì)說(shuō) C 語(yǔ)言。然后,當(dāng)它們需要相互對(duì)話(huà)時(shí),也就都說(shuō)起了 C 語(yǔ)言。

現(xiàn)在,C 語(yǔ)言成了編程通用語(yǔ)言。它不再僅僅是一種編程語(yǔ)言,還成了一種協(xié)議。

與 C 交互涉及哪些方面?

很明顯,幾乎每種語(yǔ)言都必須學(xué)會(huì)說(shuō) C 語(yǔ)言。那么,“說(shuō) C 語(yǔ)言”是什么意思?這是說(shuō)要以 C 語(yǔ)言頭文件的方式描述接口的類(lèi)型和函數(shù),并以某種方式做一些事情:

  • 匹配這些類(lèi)型的布局;

  • 用鏈接器做一些事情,將函數(shù)的符號(hào)解析為指針;

  • 用適當(dāng)?shù)?ABI 來(lái)調(diào)用這些函數(shù)(比如把參數(shù)放在正確的寄存器中)。

然而這里有兩個(gè)問(wèn)題:

  • 你不能真的編寫(xiě)一個(gè) C 解析器

  • C 并沒(méi)有一個(gè) ABI,甚至是定義好的類(lèi)型布局。

你不能真的解析一個(gè) C 頭文件

真的,解析 C 語(yǔ)言基本上是不可能的。

“但是,等等!有很多工具可以讀取 C 語(yǔ)言的頭文件,比如 rust-bindgen!”

但還是不行:

bindgen 使用 libclang 來(lái)解析 C 和 C++ 頭文件。要修改 bindgen 搜索 libclang 的方式,請(qǐng)參閱 clang-sys 文檔。關(guān)于 bindgen 如何使用 libclang 的更多細(xì)節(jié),請(qǐng)參閱 bindgen 用戶(hù)指南。

任何花了大量時(shí)間嘗試從語(yǔ)法上分析 C(++) 頭文件的人,很快就會(huì)說(shuō)“啊,去他的”,并轉(zhuǎn)而用一個(gè) C(++) 編譯器來(lái)做這件事。請(qǐng)記住,僅僅從語(yǔ)法上分析 C 頭文件是沒(méi)有意義的:你還需要解析 #includes、typedefs 和 macros 的。因此,現(xiàn)在你需要實(shí)現(xiàn)平臺(tái)所有的頭文件解析邏輯,并以某種方式找到與你所關(guān)注的環(huán)境相對(duì)應(yīng)的 DEFINED 內(nèi)容。

就拿 Swift 這個(gè)極端的例子來(lái)說(shuō)吧。在 C 語(yǔ)言互操作和資源方面,它基本上擁有一切優(yōu)勢(shì)。

該語(yǔ)言是由蘋(píng)果公司開(kāi)發(fā)的,它有效地取代了 Objective-C,成為在蘋(píng)果平臺(tái)上定義和使用系統(tǒng) API 的主語(yǔ)言。我認(rèn)為,在這個(gè)過(guò)程中,它在 ABI 穩(wěn)定性和設(shè)計(jì)方面比其他任何語(yǔ)言都更進(jìn)一步。

它也是我見(jiàn)過(guò)的對(duì) FFI 支持最好的語(yǔ)言之一。它可以本地導(dǎo)入 (Objective-)C(++) 頭文件,并生成一個(gè)漂亮的原生 Swift 接口,相關(guān)類(lèi)型會(huì)自動(dòng)“橋接”到 Swift 中對(duì)等的類(lèi)型(通常是透明的,因?yàn)檫@些類(lèi)型的 ABI 相同)。

Swift 的開(kāi)發(fā)者同時(shí)也是蘋(píng)果公司 Clang 和 LLVM 項(xiàng)目的構(gòu)建者和維護(hù)人。他們都是 C 語(yǔ)言及其衍生物方面的世界級(jí)專(zhuān)家。Doug Gregor 就是其中之一,以下是他對(duì) C FFI 的看法:

922c38f4-cc3b-11ec-bce3-dac502259ad0.png

看吧,即便是 Swift 也不愿意做這種事。(另外可以參見(jiàn) Jordan Rose 和 John McCall 在 llvm 上的 PPT 去了解“Swift 為何采用這種方式”)。

那么,如果你無(wú)論如何也不想使用 C 編譯器在編譯時(shí)分析并解析頭文件,那么你要怎么做?你就要“手工翻譯”了!int64_t?還是說(shuō)寫(xiě)i64. long?......

C 實(shí)際上并沒(méi)有 ABI

好吧,這沒(méi)什么可大驚小怪的:出于“可移植性”考慮,C 語(yǔ)言中的整數(shù)類(lèi)型被設(shè)計(jì)成大小不固定的。我們可以把賭注押在有點(diǎn)怪異的 CHAR_BIT 上,但我們還是無(wú)法知道 long 的大小和對(duì)齊方式。

”但是等等!每個(gè)平臺(tái)都有標(biāo)準(zhǔn)化的調(diào)用約定和 ABI!“

的確是有,而且它們通常定義了 C 語(yǔ)言中關(guān)鍵原語(yǔ)的布局?。ǘ?,其中一些不僅僅定義了 C 類(lèi)型的調(diào)用約定,參見(jiàn) AMD64 SysV。)

但這里有一個(gè)棘手的問(wèn)題:其架構(gòu)中并沒(méi)有定義 ABI。操作系統(tǒng)也沒(méi)有。我們必須針對(duì)特定的目標(biāo)三元組(target triple)做工作,比如“x86_64-pc-windows-gnu”(不要與“x86_64-pc-windows-msvc”弄混了)。

好吧,會(huì)有多少個(gè)這樣的目標(biāo)三元組呢?

> rustc --print target-list
aarch64-apple-darwinaarch64-apple-iosaarch64-apple-ios-macabiaarch64-apple-ios-simaarch64-apple-tvos...

還有:

...armv7-unknown-linux-musleabiarmv7-unknown-linux-musleabihfarmv7-unknown-linux-uclibceabihf...

還有:

...x86_64-uwp-windows-gnux86_64-uwp-windows-msvcx86_64-wrs-vxworks
>_

這樣的目標(biāo)三元組總共有176個(gè)。我原本打算都列出來(lái),以增強(qiáng)視覺(jué)沖擊,但實(shí)在是太多了。

ABI 實(shí)在是太多了。而且,我們還沒(méi)有涉及到所有不同的調(diào)用約定,比如 stdcall vs fastcall 或者 aapcs vs aapcs-vfp!

至少,所有這些 ABI 和調(diào)用約定之類(lèi)的東西肯定要以機(jī)器可讀的格式提供給大家使用:冗長(zhǎng)的 PDF 文件。

好吧,至少對(duì)于特定的目標(biāo)三原組,主要的 C 語(yǔ)言編譯器在 ABI 上達(dá)成了一致!當(dāng)然,也有一些奇怪的 C 語(yǔ)言編譯器,如 clang 和 gcc-。

> abi-checker --tests ui128 --pairs clang_calls_gcc gcc_calls_clang
...
Test ui128::i128_val_in_0_perturbed_small        passedTest ui128::i128_val_in_1_perturbed_small        passedTest ui128::i128_val_in_2_perturbed_small        passedTest ui128::i128_val_in_3_perturbed_small        passedTest ui128::i128_val_in_0_perturbed_big          failed!test 57 arg3 field 0 mismatchcaller: [30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 3A, 3B, 3C, 3D, 3E, 3F]callee: [38, 39, 3A, 3B, 3C, 3D, 3E, 3F, 40, 41, 42, 43, 44, 45, 46, 47]Test ui128::i128_val_in_1_perturbed_big          failed!test 58 arg3 field 0 mismatchcaller: [30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 3A, 3B, 3C, 3D, 3E, 3F]callee: [38, 39, 3A, 3B, 3C, 3D, 3E, 3F, 40, 41, 42, 43, 44, 45, 46, 47]
...
392passed,60failed,0completelyfailed,8skipped

這是我在 x64 Ubuntu 20.04 上運(yùn)行 FFI abi-checker 的結(jié)果。這是一個(gè)相當(dāng)重要的、表現(xiàn)良好的平臺(tái)。這里測(cè)試的是一些非常令人厭煩的情況,即一些整型參數(shù)在兩個(gè)由 clang 和 gcc 編譯的靜態(tài)庫(kù)之間按值傳遞……而且失敗了!

甚至是 x64 linux 上的__int128ABI,clang 和 gcc 也未能達(dá)成一致。該類(lèi)型是一個(gè) gcc 擴(kuò)展,但 AMD64 SysV ABI 在一個(gè)不錯(cuò)的 PDF 文件里做了明確定義和說(shuō)明。

我寫(xiě)這個(gè)東西是為了檢查 rustc 中的錯(cuò)誤,我并沒(méi)有指望發(fā)現(xiàn),這兩個(gè)主要的 C 編譯器在最重要同時(shí)人們也最熟悉的 ABI 上存在不一致!

ABI 就是謊言。

試著把 C 馴化

因此,對(duì) C 語(yǔ)言頭文件做語(yǔ)義解析是一個(gè)可怕的噩夢(mèng),只能由那個(gè)平臺(tái)的 C 編譯器來(lái)完成,即使你讓 C 編譯器告訴你類(lèi)型以及如何理解注釋?zhuān)珜?shí)際上,你仍然無(wú)法知道所有東西的大小 / 對(duì)齊方式 / 調(diào)用約定。

如何與那堆東西進(jìn)行互操作呢?

你的第一個(gè)選項(xiàng)是完全投降,將你的語(yǔ)言與 C 語(yǔ)言進(jìn)行靈魂綁定,可以采用以下任何一種方式:

  • 用 C(++) 編寫(xiě)編譯器 / 運(yùn)行時(shí),所以它無(wú)論如何都能說(shuō) C 語(yǔ)言。

  • 讓你的“codegen”直接生成 C(++),這樣用戶(hù)就需要一個(gè) C 編譯器。

  • 基于一個(gè)成熟的主流 C 編譯器(gcc 或 clang)構(gòu)建自己的編譯器。

但也僅限于此,因?yàn)槌悄愕恼Z(yǔ)言真的暴露了 unsigned long long,否則你就會(huì)繼承 C 的可移植性混亂。

于是,我們來(lái)到了第二個(gè)選項(xiàng):撒謊、欺騙和偷竊。

如果這一切是一場(chǎng)躲不開(kāi)的災(zāi)難,那么還不如開(kāi)始在自己的語(yǔ)言中手工翻譯類(lèi)型和接口定義。這基本上就是我們?cè)?Rust 中每天都在做的事情。是的,人們使用 rust-bindgen 之類(lèi)的工具來(lái)自動(dòng)化這個(gè)過(guò)程,但很多時(shí)候,還是需要檢查或手工調(diào)整那些定義,生命短暫,實(shí)在無(wú)法讓經(jīng)過(guò)某人奇怪定制的 C 構(gòu)建系統(tǒng)可移植。

嘿,Rust,在 x64 linux 上 intmax_t 是什么?

pubtypeintmax_t=i64;

嘿,Nim,在 x64 linux 上 long long 是什么?

clonglong{.importc:"longlong",nodecl.}=int64

很多代碼已經(jīng)從各個(gè)環(huán)節(jié)中剔除了 C,并且已經(jīng)開(kāi)始對(duì)核心類(lèi)型的定義進(jìn)行硬編碼。畢竟,它們顯然只是平臺(tái) ABI 的一部分!它們要做什么?改變 intmax_t 的大小嗎???這顯然是一個(gè)破壞 ABI 的修改。

哦,對(duì)了,phantomderp 正在研究的那個(gè)東西又是什么?

我們談下為什么不能修改intmax_t,因?yàn)槿绻覀儚?span style="max-width:100%;white-space:pre-wrap;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace;font-size:14px;text-align:left;background-color:rgba(0,0,0,.03);color:rgb(51,51,51);">longlong(64位整數(shù))改為__int128_t(128 位整數(shù)),某些二進(jìn)制文件就會(huì)無(wú)所適從,使用錯(cuò)誤的調(diào)用約定/返回約定。但是,有沒(méi)有一種方法——如果代碼選用了——我們可以在新的應(yīng)用程序中升級(jí)函數(shù)調(diào)用,而讓老的應(yīng)用程序保持原樣?讓我們編寫(xiě)一些代碼,測(cè)試一下透明別名可以為 ABI 帶來(lái)什么幫助。

是的,他們的文章真的寫(xiě)得很好,解決了一些非常重要的實(shí)際問(wèn)題,但是...... 編程語(yǔ)言如何處理這種變化?如何指定與哪個(gè)版本的 intmax_t 互操作?如果有一些 C 語(yǔ)言頭文件涉及到了 intmax_t,它使用哪個(gè)定義?

我們?cè)谟懻?ABI 不同的平臺(tái)時(shí)使用的主要機(jī)制是目標(biāo)三元組。你知道什么是目標(biāo)三元組嗎?x86_64-unknown-linux-gnu。你知道都包括什么嗎?基本上涵蓋了過(guò)去 20 年里所有主要的桌面 / 服務(wù)器 Linux 發(fā)行版。表面上,你可以針對(duì)某個(gè)目標(biāo)進(jìn)行編譯,并得到一個(gè)在所有這些平臺(tái)上都能“正常工作”的二進(jìn)制文件。但是,情況可能并非如此,比如有些程序在編譯時(shí)會(huì)默認(rèn) intmax_tint64_t 大。

任何試圖做出這種改變的平臺(tái)是不是都會(huì)成為一個(gè)新的目標(biāo)三元組?x86_64-unknown-linux-gnu2?如果任何針對(duì) x86_64-unknown-linux-gnu 編譯的東西都可以在上面運(yùn)行,這還不夠嗎?

修改簽名而又不破壞 ABI

”那又怎樣,難道 C 語(yǔ)言就永遠(yuǎn)不會(huì)再改進(jìn)了嗎?“

說(shuō)不是也是,因?yàn)樗愀獾脑O(shè)計(jì)。

老實(shí)說(shuō),進(jìn)行 ABI 兼容的修改可謂是一種藝術(shù)形式。這項(xiàng)工作的一部分是準(zhǔn)備。如果你準(zhǔn)備好了,做不破壞 ABI 的修改就會(huì)簡(jiǎn)單很多。

正如 phantomderp 的文章所指出的那樣,像 glibc(gx86_64-unknown-linux-gnu 中的 gnu)早就意識(shí)到了這一點(diǎn),并使用符號(hào)版本化這樣的機(jī)制來(lái)更新簽名和 API,同時(shí)為任何針對(duì)舊版本的編譯保留舊版本。

因此,如果有個(gè)方法 int32_t my_rad_symbol(int32_t),你告訴編譯器將其導(dǎo)出為 my_rad_symbol_v1,那么任何針對(duì)你所提供的頭文件進(jìn)行編譯的人,都會(huì)在代碼中寫(xiě)上 my_rad_symbol,但會(huì)鏈接到 my_rad_symbol_v1。

然后,當(dāng)你確定實(shí)際應(yīng)該使用 int64_t 時(shí),可以把 int64_t my_rad_symbol(int64_t) 當(dāng)作 my_rad_symbol_v2,但仍然保留舊的定義 my_rad_symbol_v1。任何人在針對(duì)你的頭文件進(jìn)行編譯時(shí),如果是針對(duì)新版本就使用符號(hào) v2,而針對(duì)舊版本則繼續(xù)使用 v1!

但仍然有一個(gè)兼容性問(wèn)題:任何針對(duì)新的頭文件所做的編譯都不能與舊版本的庫(kù)進(jìn)行鏈接!庫(kù)的 v1 版本根本沒(méi)有 v2 符號(hào)。所以,如果你想要熱門(mén)的新功能,就需要接受與舊有系統(tǒng)不兼容的事實(shí)。

不過(guò),這并不是什么大問(wèn)題,只是會(huì)讓平臺(tái)供應(yīng)商感到難過(guò),因?yàn)闆](méi)有人能夠立即使用他們花了這么多時(shí)間做出來(lái)的東西。你推出了一個(gè)閃亮的新特性,卻要放在手里等數(shù)年的時(shí)間,等到大家認(rèn)為它變得足夠普及 / 成熟,愿意依賴(lài)它并打破對(duì)舊平臺(tái)的支持(或者愿意為它實(shí)現(xiàn)動(dòng)態(tài)檢查和回退)。

如果你想讓人們立即升級(jí),那么就是向前兼容的問(wèn)題了。這就需要讓舊版本能夠適應(yīng)它們完全沒(méi)有概念的新特性。

修改類(lèi)型而不破壞 ABI

好了,除了修改函數(shù)的簽名,我們還可以修改什么?我們可以修改類(lèi)型布局嗎?

可以!但也不可以!這取決于你暴露類(lèi)型的方式。

C 語(yǔ)言真正奇妙的其中一個(gè)功能是,它讓你可以區(qū)分布局已知的類(lèi)型和布局未知的類(lèi)型。如果你只在 C 語(yǔ)言的頭文件中前向聲明一個(gè)類(lèi)型,那么任何與該類(lèi)型交互的用戶(hù)代碼都無(wú)法知道該類(lèi)型的布局,而必須一直通過(guò)指針不透明地對(duì)它做處理。

所以你可以開(kāi)發(fā)一個(gè)像 MyRadType*make_val() use_val(MyRadType) 這樣的 API,然后利用同樣的符號(hào)版本化技巧來(lái)暴露 make_val_v1 和 use_val_v1,任何時(shí)候你想修改這個(gè)布局,都要在與該類(lèi)型交互的所有東西上修改版本。同樣地,你得保留 MyRadTypeV1、MyRadTypeV2 和一些類(lèi)型定義,以確保人們使用“正確”的類(lèi)型。

很好,我們可以改變不同版本之間的類(lèi)型布局!對(duì)嗎?嗯,大多數(shù)時(shí)候是這樣。

如果有多個(gè)東西基于你的庫(kù)構(gòu)建,它們?cè)陬?lèi)型不透明的情況下相互調(diào)用,就會(huì)出現(xiàn)糟糕的情況:

  • lib1:開(kāi)發(fā)一個(gè) API,使用類(lèi)型 MyRadType* 調(diào)用 use_val;

  • lib2:調(diào)用 make_val ,并將結(jié)果傳給 lib1。

如果 lib1 和 lib2 是基于庫(kù)的不同版本進(jìn)行編譯的,那么 make_val_v1 就會(huì)被傳遞給 use_val_v2!這時(shí),你有兩個(gè)選擇來(lái)處理這個(gè)問(wèn)題:

  1. 禁止這樣做,警告那些這樣做的人,令人傷心。

  2. 以一種向前兼容的方式設(shè)計(jì) MyRadType,這樣混用就沒(méi)問(wèn)題了。

實(shí)現(xiàn)向前兼容常用的技巧有:

  • 保留未使用的字段供未來(lái)版本使用。

  • MyRadType 的所有版本都有一個(gè)共同的前綴,讓你可以“檢查”所使用的版本。

  • 有大小自適應(yīng)的字段,這樣舊版本可以“跳過(guò)”新增部分。

    案例分析:MINIDUMP_HANDLE_DATA

微軟確實(shí)是向前兼容的大師,他們甚至讓他們真正關(guān)心的東西在不同的架構(gòu)之間保持布局兼容。我最近遇到的一個(gè)例子是 Minidumpapiset.h 中的 MINIDUMP_HANDLE_DATA_STREAM。

這個(gè) API 描述了一個(gè)版本化的值列表。該列表以這種類(lèi)型開(kāi)始:

typedef struct _MINIDUMP_HANDLE_DATA_STREAM {    ULONG32 SizeOfHeader;    ULONG32 SizeOfDescriptor;    ULONG32 NumberOfDescriptors;    ULONG32 Reserved;}MINIDUMP_HANDLE_DATA_STREAM,*PMINIDUMP_HANDLE_DATA_STREAM;

其中:

  • SizeOfHeader 是 MINIDUMP_HANDLE_DATA_STREAM 本身的大小。如果需要在末尾添加更多的字段,那也沒(méi)關(guān)系,因?yàn)榕f版本可以使用這個(gè)值來(lái)檢測(cè)頭的“版本”,并跳過(guò)任何它們不識(shí)別的字段。

  • SizeOfDescriptor 是數(shù)組中每個(gè)元素的大小。這也是為了讓你知道元素是什么“版本”,你可以跳過(guò)不知道的字段。

  • NumberOfDescriptors 是數(shù)組長(zhǎng)度。

  • Reserved 是一個(gè)保留字段(Minidumpapiset.h 非常嚴(yán)謹(jǐn),從不使用任何填充字節(jié),因?yàn)樘畛渥止?jié)的值未定,而且是一種序列化的二進(jìn)制文件格式。我希望他們添加這個(gè)字段是為了使結(jié)構(gòu)的大小是 8 的倍數(shù),這樣就不會(huì)有數(shù)組元素是否需要在頭之后填充的問(wèn)題了。哇,這才是認(rèn)真對(duì)待兼容性!)

事實(shí)上,微軟使用這種版本化方案是有原因的,他們定義了兩個(gè)版本的數(shù)組元素:

typedef struct _MINIDUMP_HANDLE_DESCRIPTOR {    ULONG64 Handle;    RVA TypeNameRva;    RVA ObjectNameRva;    ULONG32 Attributes;    ULONG32 GrantedAccess;    ULONG32 HandleCount;    ULONG32 PointerCount;} MINIDUMP_HANDLE_DESCRIPTOR, *PMINIDUMP_HANDLE_DESCRIPTOR;
typedef struct _MINIDUMP_HANDLE_DESCRIPTOR_2 {    ULONG64 Handle;    RVA TypeNameRva;    RVA ObjectNameRva;    ULONG32 Attributes;    ULONG32 GrantedAccess;    ULONG32 HandleCount;    ULONG32 PointerCount;    RVA ObjectInfoRva;    ULONG32 Reserved0;} MINIDUMP_HANDLE_DESCRIPTOR_2, *PMINIDUMP_HANDLE_DESCRIPTOR_2;
// 最新MINIDUMP_HANDLE_DESCRIPTOR定義。typedef MINIDUMP_HANDLE_DESCRIPTOR_2 MINIDUMP_HANDLE_DESCRIPTOR_N;typedefMINIDUMP_HANDLE_DESCRIPTOR_N*PMINIDUMP_HANDLE_DESCRIPTOR_N;

關(guān)于這些結(jié)構(gòu)的實(shí)際細(xì)節(jié),有幾個(gè)比較有趣的地方:

  • 對(duì)它的修改只是在末尾添加字段;

  • “最后一個(gè)”有類(lèi)型定義;

  • 保留一些 Maybe Padding(RVA 是 ULONG32 類(lèi)型)。

在向前兼容性方面,微軟絕對(duì)是一頭堅(jiān)不可摧的巨獸。他們對(duì)填充如此謹(jǐn)慎,甚至在 32 位和 64 位之間采用了相同的布局!(實(shí)際上,這非常重要,因?yàn)槟阆M粋€(gè)架構(gòu)的小型轉(zhuǎn)儲(chǔ)文件處理器能夠處理每個(gè)架構(gòu)的小型轉(zhuǎn)儲(chǔ)文件。)

好吧,至少它真的很健壯,如果你按照它的規(guī)則來(lái),通過(guò)引用進(jìn)行操作,并使用 size 字段。

但至少可以玩下去。只是在某些時(shí)候,你不得不說(shuō)“你的用法不對(duì)”。微軟可能不會(huì)這么說(shuō),他們只會(huì)做一些可怕的事。

案例分析:jmp_buf

我對(duì)這種情況不是很熟悉,但在研究 glibc 歷史上的破壞性修改時(shí),我在 lwn 上看到了這篇很棒的文章:glibc s390 ABI 的破壞性修改。我認(rèn)為這篇文章比較準(zhǔn)確。

事實(shí)證明,glibc 曾經(jīng)破壞過(guò)類(lèi)型的 ABI,至少在 s390 上是這樣。根據(jù)這篇文章的描述,它造成了混亂。

特別地,他們改變了 setjmp/longjmp 使用的狀態(tài)保存類(lèi)型(即 jmp_buf)的布局??窗?,他們并不是十足的傻瓜。他們知道這是一個(gè)破壞 ABI 的修改,所以他們負(fù)責(zé)任地做了符號(hào)版本化。

但是,jmp_buf 并不是一個(gè)不透明類(lèi)型。有些東西內(nèi)聯(lián)地存儲(chǔ)了這個(gè)類(lèi)型的實(shí)例,比如 Perl 的運(yùn)行時(shí)。不用說(shuō),這個(gè)相比之下不是很容易理解的類(lèi)型已經(jīng)滲透到許多二進(jìn)制文件中去了,最終的結(jié)論是,Debian 的所有東西都需要重新編譯。

這篇文章甚至討論了對(duì) libc 進(jìn)行版本升級(jí)以應(yīng)對(duì)這種情況的可能性:

在像 Debian 這樣的混合 ABI 環(huán)境中,SO 名稱(chēng)的改變(SO name bump)會(huì)導(dǎo)致兩個(gè) libc 被加載并競(jìng)爭(zhēng)相同的符號(hào)命名空間,而解析(以及 ABI 選擇)由 ELF 插值和作用域規(guī)則決定。這真是一場(chǎng)噩夢(mèng)。這可能是一個(gè)比告訴所有人重新構(gòu)建并回歸正常軌道更糟糕的解決方案。

(這篇文章很不錯(cuò),強(qiáng)烈建議您讀一下。)

真的能修改 intmax_t?

在我看來(lái),未必。和 jmp_buf 一樣,它不是一個(gè)不透明類(lèi)型,也就是說(shuō),它被大量的隨機(jī)結(jié)構(gòu)內(nèi)聯(lián),被其他大量的語(yǔ)言和編譯器視為一個(gè)特定的表示,并且可能存在于大量的公共接口中,而這些接口不在 libc、linux、甚至發(fā)行版維護(hù)者的控制之下。

當(dāng)然,libc 可以適當(dāng)?shù)厥褂梅?hào)版本化技巧,使其 API 可以適應(yīng)新的定義,但是,改變一個(gè)基本數(shù)據(jù)類(lèi)型(像 intmax_t)的大小,會(huì)在更大的平臺(tái)生態(tài)系統(tǒng)中引發(fā)混亂。

如果有人能夠證明我是錯(cuò)的,我會(huì)很高興,但據(jù)我所知,做出這樣的改變需要一個(gè)新的目標(biāo)三元組,并且不允許任何為舊 ABI 構(gòu)建的二進(jìn)制文件 / 庫(kù)在這個(gè)新三元組上運(yùn)行。當(dāng)然,你可以這樣做,但我并不羨慕任何做了這些工作的發(fā)行版。

即使如此,還有 x64 int 的問(wèn)題:它是非?;镜念?lèi)型,而且長(zhǎng)期以來(lái)大小從沒(méi)變過(guò),無(wú)數(shù)的應(yīng)用程序可能對(duì)它做了無(wú)法察覺(jué)的假設(shè)。這就是為什么 int 在 x64 上是 32 位的,盡管它“應(yīng)該”是 64 位的:int 長(zhǎng)期以來(lái)都是 32 位,以至于將軟件升級(jí)到新的大小完全無(wú)望,盡管它是一個(gè)全新的架構(gòu)和目標(biāo)三元組。

我也希望我的觀(guān)點(diǎn)是錯(cuò)的。如果 C 語(yǔ)言只是一種獨(dú)立的編程語(yǔ)言,那我們就可以毫無(wú)顧慮地往前沖。但它實(shí)際上不是了,它是一個(gè)協(xié)議,還是一個(gè)糟糕的協(xié)議,而我們還必須要用它。

很遺憾,C,你征服了世界,但或許不再擁有往昔的美好。

原文鏈接:https://gankra.github.io/blah/c-isnt-a-language

審核編輯 :李倩
聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀(guān)點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 操作系統(tǒng)
    +關(guān)注

    關(guān)注

    37

    文章

    6850

    瀏覽量

    123432
  • C語(yǔ)言
    +關(guān)注

    關(guān)注

    180

    文章

    7608

    瀏覽量

    137125
  • 編程語(yǔ)言
    +關(guān)注

    關(guān)注

    10

    文章

    1947

    瀏覽量

    34819

原文標(biāo)題:“C不再是一種編程語(yǔ)言”

文章出處:【微信號(hào):mcu168,微信公眾號(hào):硬件攻城獅】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    gitee 支持的編程語(yǔ)言有哪些

    些 Gitee 支持的常見(jiàn)編程語(yǔ)言: Python :一種廣泛使用的高級(jí)編程語(yǔ)言,以其清晰的語(yǔ)法和代碼可讀性而聞名。 Java :
    的頭像 發(fā)表于 01-06 09:50 ?76次閱讀

    Triton編譯器支持的編程語(yǔ)言

    編寫(xiě)和優(yōu)化深度學(xué)習(xí)代碼。Python是一種廣泛使用的高級(jí)編程語(yǔ)言,具有簡(jiǎn)潔易讀、易于上手、庫(kù)豐富等特點(diǎn),非常適合用于深度學(xué)習(xí)應(yīng)用的開(kāi)發(fā)。 二、領(lǐng)域特定語(yǔ)言(DSL) Triton也提供
    的頭像 發(fā)表于 12-24 17:33 ?378次閱讀

    NPU支持的編程語(yǔ)言有哪些

    NPU(Neural Processing Unit)是一種專(zhuān)門(mén)為深度學(xué)習(xí)和人工智能應(yīng)用設(shè)計(jì)的處理器。NPU支持的編程語(yǔ)言通常與它所集成的平臺(tái)或框架緊密相關(guān)。以下是些常見(jiàn)的
    的頭像 發(fā)表于 11-15 09:21 ?754次閱讀

    C語(yǔ)言中的socket編程基礎(chǔ)

    Socket編程簡(jiǎn)介 Socket是一種通信機(jī)制,允許程序之間進(jìn)行通信。在C語(yǔ)言中,socket編程是網(wǎng)絡(luò)
    的頭像 發(fā)表于 11-01 16:51 ?378次閱讀

    MCU編程語(yǔ)言和開(kāi)發(fā)環(huán)境介紹

    MCU編程語(yǔ)言 MCU編程語(yǔ)言是用于編寫(xiě)MCU程序的高級(jí)編程語(yǔ)言,它們使得開(kāi)發(fā)者能夠更高效地開(kāi)
    的頭像 發(fā)表于 11-01 11:51 ?875次閱讀

    C語(yǔ)言與其他編程語(yǔ)言的比較

    C語(yǔ)言作為一種歷史悠久的編程語(yǔ)言,自其誕生以來(lái),直在軟件開(kāi)發(fā)領(lǐng)域扮演著重要角色。它以其高效、靈
    的頭像 發(fā)表于 10-29 17:30 ?305次閱讀

    Orin芯片的編程語(yǔ)言支持

    語(yǔ)言支持 Orin芯片支持多種編程語(yǔ)言,以滿(mǎn)足不同開(kāi)發(fā)者的需求。其中,C/C++和Python是兩
    的頭像 發(fā)表于 10-27 16:45 ?329次閱讀

    labview是什么編程語(yǔ)言寫(xiě)的

    一種圖形化編程語(yǔ)言。它并不是用傳統(tǒng)的文本編程語(yǔ)言(如C++、Python等)編寫(xiě)的,而是采用了
    的頭像 發(fā)表于 09-04 16:00 ?990次閱讀

    plc編程st語(yǔ)言怎么編

    PLC(可編程邏輯控制器)編程中的ST(Structured Text)語(yǔ)言一種高級(jí)編程語(yǔ)言
    的頭像 發(fā)表于 08-25 10:05 ?1306次閱讀

    PLC編程語(yǔ)言C語(yǔ)言的區(qū)別

    在工業(yè)自動(dòng)化和計(jì)算機(jī)編程領(lǐng)域中,PLC(可編程邏輯控制器)編程語(yǔ)言C語(yǔ)言各自扮演著重要的角色。
    的頭像 發(fā)表于 06-14 17:11 ?2994次閱讀

    介紹C語(yǔ)言中錯(cuò)誤處理和異常處理的些常用的方法和策略

    C語(yǔ)言一種低級(jí)的、靜態(tài)的、結(jié)構(gòu)化的編程語(yǔ)言,它沒(méi)有提供像C++或Java等高級(jí)
    的頭像 發(fā)表于 02-28 14:25 ?642次閱讀

    plc編程語(yǔ)言c語(yǔ)言的聯(lián)系 c語(yǔ)言和PLC有什么區(qū)別

    PLC編程語(yǔ)言C語(yǔ)言的聯(lián)系 PLC(可編程邏輯控制器)是一種針對(duì)自動(dòng)化控制系統(tǒng)的特殊計(jì)算機(jī)。P
    的頭像 發(fā)表于 02-05 14:21 ?4224次閱讀

    編程語(yǔ)言之間的區(qū)別和聯(lián)系

    編程語(yǔ)言一種人與計(jì)算機(jī)之間進(jìn)行交流的方式,不同的編程語(yǔ)言有著不同的特點(diǎn)和用途。本文將對(duì)四常見(jiàn)
    的頭像 發(fā)表于 02-05 14:16 ?1616次閱讀

    c語(yǔ)言,c++,java,python區(qū)別

    C語(yǔ)言、C++、Java和Python是四常見(jiàn)的編程語(yǔ)言,各有優(yōu)點(diǎn)和特點(diǎn)。
    的頭像 發(fā)表于 02-05 14:11 ?2459次閱讀

    vb語(yǔ)言c++語(yǔ)言的區(qū)別

    Microsoft開(kāi)發(fā)的一種面向?qū)ο蟮氖录?qū)動(dòng)編程語(yǔ)言。它的設(shè)計(jì)目標(biāo)是簡(jiǎn)化編程過(guò)程,讓初學(xué)者也能快速上手。與之相比,C++
    的頭像 發(fā)表于 02-01 10:20 ?2387次閱讀