← 所有文章

工程哲學:Bjarne Stroustrup,沒有額外開銷的抽象

Bjarne Stroustrup,C++ 程式語言的創造者

重點摘要

  • 沒有額外開銷的抽象。 Stroustrup 打造 C++,就是要終結「清晰程式碼」與「貼近硬體的速度」之間的假性抉擇——富表達力的寫法與手工調校的寫法,應該編譯出相同的機器碼。
  • 零開銷原則。 「沒用到的,就不必付出代價;用到的,你也無法手寫得更好」——這是每一項 C++ 功能都必須通過的準則。
  • RAII 讓原則落地。 把資源的生命週期綁定在堆疊物件上,便能在不依賴垃圾回收的情況下,達成確定性、例外安全的清理——這個模式後來幾乎被 Rust 原封不動地採納。
  • 底下不留餘地。 C++ 必須穩居最高階語言之位,在它與矽晶片之間,除了組合語言外不容任何東西插足,否則就會有更低階的語言取而代之。

原則

「沒用到的,就不必付出代價。更進一步:用到的,你也無法手寫得更好。」——Bjarne Stroustrup,零開銷原則1

這句話是系統程式設計中最重要的一句,它承載著兩個承諾。前半句說的是:抽象在「不」使用時必須完全免費——動用某項功能會付出代價,忽略它則分文不取,不耗一個位元組的記憶體,不費一個執行週期。後半句則是更艱難的承諾:當你「真的」用到某項抽象時,編譯器產生的機器碼,至少要與一位稱職的程式設計師為同一件事手寫出來的一樣好。一個在 release 模式下完全不做邊界檢查的 vector。一個編譯後等同於裸指標的智慧指標。一個消融進原始迴圈、不留痕跡的迭代器。清晰是免費的。

多數語言都逼你做取捨。你要嘛選用高階、富表達力但緩慢的版本,要嘛退守低階、快速卻難以閱讀的版本——於是整門手藝就淪為:今天該忍受哪一種痛苦。Stroustrup 的整個職涯,就是對這個取捨的拒絕。他親身體會過那份痛:攻讀博士時,他熱愛 Simula 的抽象,卻嫌它在實務上太慢;熱愛 BCPL 的速度,卻嫌它太低階,無法用來打造任何龐大的東西。2C++ 之所以存在,正是因為他認定:你根本不該被迫選擇。效率與抽象並非兩個要彼此權衡的對立旋鈕;它們本應一同到來,出現在同一行程式碼裡。

這份信念,與另一個論點底下的信念如出一轍——效能不是你額外加上去的功能,而是一種你沒能守住就會失去的特性。零開銷的抽象,並不是在一個緩慢語言之上疊出來的快速東西。它是一個「清晰」的東西,編譯器把它輾磨回你原本會手寫出來的樣子,讓你永遠不必為清晰付費。「我喜歡我的程式碼既優雅又有效率,」他說過——而其中關鍵的字眼,是那個「又」。他痛恨被迫在兩者之間取捨。3

背景脈絡

Bjarne Stroustrup 於 1950 年 12 月 30 日生於丹麥的奧胡斯(Aarhus)。4他在奧胡斯大學主修數學與電腦科學,之後前往英國,在劍橋大學運算實驗室攻讀博士,於 1979 年完成學業。他的論文——「分散式電腦系統中的通訊與控制」——探討的是如何把一個作業系統的工作,分散到由多台電腦組成的網路上。4

為了研究這個問題,他需要打造一個模擬器,而他用 Simula 寫出了第一個版本。Simula 是那套由 Ole-Johan Dahl 與 Kristen Nygaard 設計的挪威語言,正是它把類別(class)與物件(object)的概念帶給了世界。Stroustrup 對它一見傾心。Simula 的類別抽象讓他能依照「問題」本身的組織方式去組織一個龐大而精密的程式,而非依照機器的方式——這個程式寫起來、改起來都是一種享受。然而當他試著大規模執行它時,這些抽象卻在自身的代價下崩潰了。它的實作慢到不切實際。他改用 BCPL 重寫模擬器,這是一個被精簡到極致的系統語言,速度回來了——但 BCPL 太過低階,用它打造任何龐大的東西都是一場苦役。2這份劇烈的落差,成了他塑形的傷口。他用過富表達力的語言,代價是效能;又用過快速的語言,代價是其他的一切。兩者都無法接受。這兩半本應能夠同時成立。

1979 年,他帶著這份怨懟來到紐澤西的 貝爾實驗室(Bell Labs),在那裡再度面對一個分散式系統的問題:分析 UNIX 核心在網路上的流量。記取劍橋的教訓,他著手把 Simula 的組織能力加進 C——一個本就快速、貼近硬體、身為 UNIX 系統語言的語言——而且一分一毫都不放棄 C 的速度。45他把成果稱為 「C with Classes」。1983 年,他的同事 Rick Mascitti 提議用 C++ 這個名字,也就是把遞增運算子套用在 C 自身上:下一個 C,進步一階,底下還是同一台機器。5

Bjarne Stroustrup 演講中

他的工作

從 C with Classes 到 C++:一個不必付出代價的更好的 C

最初的這一步(1979–1983)刻意地保守,卻也恰到好處。拿 C——本就快速、本就直接對應硬體、本就是眾人信賴的系統語言——再加上 Simula 教會他去追求的東西:類別、更好的型別檢查、資料抽象。5關鍵在於,他把這些東西加成「一個 C 程式設計師不必為之付費的層次」。一個 C 程式仍然是合法的 C++ 程式。一個完全沒用到新功能的 struct,編譯出來的位元組和它一向產生的一模一樣。新的能力是「選用」的,而使用它所付出的代價,僅僅等於你手寫等價 C 程式碼會付出的代價。這就是零開銷原則的雛形,在他為它命名之前便已存在:別對不使用功能的人課稅,也別讓功能產生比他們自己會寫的更差的程式碼。

他在《The C++ Programming Language》(1985)一書中記錄並穩定了這門語言,正是這本書讓它得以普及;接著,他花了數十年,透過每一項後續功能的設計來捍衛它的核心。4

零開銷原則

零開銷原則是那個承重的核心概念,是衡量每一項 C++ 功能的準繩。Stroustrup 把它分成兩部分陳述:「沒用到的,就不必付出代價(無論時間或空間);更進一步:用到的,你也無法手寫得更好。」1第一個子句是一種課稅政策——任何功能都不得對不使用它的程式碼施加成本。第二個子句是一道品質門檻——你「真的」用到的功能,編譯出來的程式碼必須不遜於一位謹慎的程式設計師為那項特定工作親手寫出的成果。templates、constexpr、move 語意,以及標準函式庫的容器與演算法,全都是為了恪守它而設計的;而兩個著名的例外——執行期型別資訊(RTTI)與例外處理(exceptions)——恰恰就是編譯器提供開關讓你關閉的那兩項功能,「正因為」它們扭曲了這條規則。6

回報則是那個聽起來不可能、直到你親眼看見組合語言才會信服的結果:富表達力的寫法與手工調校的寫法,產生相同的機器碼。

Bjarne Stroustrup

正是這份同一性——富表達力與手工調校塌縮為同一份輸出——讓這條原則不只是技術上的,更是道德上的。它剝奪了工程師慣用的藉口。倘若清晰真的所費不貲,你大可理直氣壯地寫出醜陋而快速的版本,「因為它非快不可」。Stroustrup 的賭注是:這個藉口通常是個謊言——一個設計良好的抽象,能讓你同時擁有可讀的版本「與」快速的版本,因為它們本就是同一個版本。代價已經被付過一次了,由語言設計者承擔,好讓程式設計師再也不必付第二次。

RAII 與確定性的資源管理

這條原則需要一套機制,來應對系統程式設計中最棘手的那一塊:那些必須被取得、然後再被可靠釋放的資源——記憶體、檔案、鎖、socket。垃圾回收的答案是放棄確定性:清理「終究」會發生,但要看回收器的排程,這對一個鎖或一個檔案 handle 而言難以忍受。Stroustrup 的答案,被他命名為 RAII——資源取得即初始化(Resource Acquisition Is Initialization)——把一項資源的生命週期,綁定在堆疊上某個物件的生命週期之上。7你在建構子中取得資源,在解構子中釋放它;而由於 C++ 保證一個堆疊物件的解構子會在它離開作用域的瞬間執行——無論是正常離開「或」因例外而離開,且以建構的反向順序執行——資源便能在它該被釋放的那一刻被確定性地釋放,不需回收器,也沒有手動的 free 會被遺忘。7

RAII 是零開銷原則的具體化身。那個解構子的呼叫,本來就是你無論如何都得親手寫下的程式碼;語言只是保證你不可能忘記它。你得到的是自動、例外安全、確定性的清理,而你付出的,不比自己手寫清理會付出的多上分毫。這正是後來的語言——其中以 Rust 最為顯著——幾乎原封不動採納的那個 C++ 模式。

標準化,以及「底下不為更低階的語言留餘地」

Stroustrup 並未把 C++ 留作個人專案。他把它帶進標準委員會——1989 年起的 ANSI、1991 年起的 ISO——並在其中服務了數十年,主持演進工作小組(Evolution Working Group),好讓 C++ 成為一個穩定、不偏袒任何廠商的標準,而非某一家公司的方言。4他還給了委員會一條憲法級的規則,出自他在《The Design and Evolution of C++》中闡述的設計原則:「不要在 C++ 底下為任何更低階的語言留下餘地(組合語言除外)。」8其推論精準無比——倘若你能用某個介於 C++ 與裸機之間的語言寫出更有效率的程式碼,那麼那個語言就會成為眾人首選的系統語言,而 C++ 也就失去了存在的理由。8為了保持可行,C++ 必須保留 C 對硬體的直接存取、對資料佈局的掌控,以及那些一對一對應到機器的基本型別。8在抽象與矽晶片之間,除了組合語言外不該有任何東西插足。零開銷原則,以競爭邊界的形式陳述出來,便是:做那個仍然不在桌面上留下任何餘地的最高階語言。

他的方法

這套方法就是一個賭注——「只要你把抽象設計對了,清晰與速度便是同一回事」——並以一組嚴格的規則加以落實。

用零開銷衡量每一項功能。 任何功能都不得對不使用它的程式碼課稅,任何功能也都不得產生比手寫等價物更差的程式碼。這是四十年來每一項 C++ 新增功能都必須通過的關口。16

信任程式設計師,並維持多典範。 C++ 支援程序式、物件導向、泛型與函數式風格,因為 Stroustrup 拒絕替你決定該如何組織你的問題。語言給你工具,並假定你比語言設計者更了解你的領域。6

直接對應到硬體。 保留 C 在基本型別、資料佈局與機器之間的一對一對應。抽象坐落「在」金屬之上,而不是凌駕於一個把它藏起來的執行環境之上。8

讓清理是確定性的,而非終究會發生的。 RAII 把資源的生命週期綁定在物件的生命週期上,讓釋放發生在一個已知的瞬間,例外安全,不需垃圾回收,也沒有任何東西需要記得去釋放。7

在公開場域中讓它穩定下來。 把語言交給一個國際標準委員會,而非據為己有,好讓 C++ 成為一個經久耐用的公共標準,沒有任何單一廠商能將它分岔。4

影響鏈

是誰塑造了他

Ole-Johan Dahl 與 Kristen Nygaard,透過 Simula。 Simula 教會了 Stroustrup 抽象「是為了什麼」——讓一個程式能依照問題的結構去建構——而它的緩慢,則教會了他這堂課的另一半:如果你用不起,抽象便一文不值。C++ 就是那場嘗試:留住 Simula 的恩賜,卻不付它的任何代價。(直接而塑形的影響)

BCPL,以及圍繞 C 與 UNIX 的系統程式設計文化。 BCPL 給了他相對的那份痛——純粹的速度,卻無從組織任何龐大的東西——而 C,這個貝爾實驗室裡 UNIX 的語言,則給了他那快速、對應硬體的根基,讓他得以「在其之上」打造 C++,而非取而代之。25(直接影響)

貝爾實驗室。 同一個孕育了 C 與 UNIX 的機構,給了 Stroustrup 那個催生「C with Classes」的分散式系統問題,也給了他那種研究文化,讓他能花上數年把語言做對。(塑形的影響)

是他塑造了誰

世界的基礎設施跑在他的語言上。 作業系統、瀏覽器、資料庫、遊戲引擎、編譯器、高頻交易系統、火星探測車的飛行軟體——現代運算中那些攸關效能的核心,不成比例地由 C++ 所構成。少有工程師的成果承載得起這般重量。

零成本抽象運動。 Stroustrup 的原則,被改稱為「零成本抽象」(zero-cost abstractions),成了 Rust 一項明文的奠基目標;Rust 採納了 RAII(化為所有權與 Drop)、確定性的解構,以及「高階構造必須編譯成最佳機器碼」這條規則。D 與 Carbon 等語言也都出自同一血脈。「你不該為清晰付費」這個觀念如今已是主流,而它出自他。6

貫穿的主線

John Carmack 要的是讓內層迴圈做到物理所允許的最少工作量——「光速爛透了」,機器受制於硬性的極限,每一個被浪費的週期都是一種選擇。Stroustrup 把工具交到 Carmack 手上,讓他能「可讀地」寫出那個迴圈:一個零開銷的抽象,就是那個承諾——清晰的版本與最貼近金屬的版本是同一份機器碼,於是你永遠不必抉擇。Barbara Liskov 給了抽象它的理論——資料抽象、一個型別欠它的使用者的契約、那條從 Simula 到 CLU 的血脈,即「你該針對一個東西『做什麼』來寫程式,而非它『如何運作』」——而 Stroustrup 則給了那套理論一具執行起來不付任何代價的身軀。至於 Alan Kay,他把物件想像成一台台彼此傳遞訊息的小電腦,一場延遲綁定、發生在執行期的對話;Stroustrup 則走了相反的路,抵達同一個詞:他的物件在「編譯」期就被解析完畢,消融成裸機器碼,OOP 成了一種零開銷的組織工具,而非一條活生生的訊息匯流排。同樣的詞彙,兩種哲學——一種為彈性最佳化,另一種則為執行期的那筆帳單最佳化。(系列橋接)

我從中汲取了什麼

我一直記著的這堂課是:「富表達力『或』快速」幾乎總是一個由拙劣設計強加給你的假性抉擇。每當我發現自己正在寫那個醜陋的版本,「因為它非快不可」,Stroustrup 的原則就是那道指控:那並非運算的定律,而是你沒能找到那個能自行編譯成快速結果的抽象。真正的工作,是把清晰的版本設計得夠好,好讓機器從中產生最佳的程式碼——把代價在「設計」中付過一次,好讓每一行用到它的程式碼都不必再付第二次。這和品質是唯一的變數是同一道標準:該問的從來不是「我付得起讓這東西清晰的代價嗎?」而是「我的抽象怎麼漏成這樣,以致清晰竟要付出任何代價?」

在我如今打造東西的這個世界裡——代理(agents)、工具迴圈、AI 系統——誘惑恰恰與零開銷相反:在一層包裝上再疊一層包裝,動用一次模型呼叫來掩飾一條緩慢的路徑,任由每一個圖方便的抽象悄悄添上沒人測量過的延遲。Stroustrup 式的做法,是去要求那個方便的介面編譯成那個有效率的介面——讓乾淨的 API 與快速的路徑成為同一條路徑,把代價付在代理框架的設計裡,而不是向流經它的每一次呼叫收費。那份信念——即品味是一套技術系統,你能拿一條硬性規則去衡量它,而非一種你信口宣稱的氛圍——正是那條主線,從一個 1979 年跑得太慢的模擬器,一路貫穿到一個 2026 年不該跑得太慢的代理框架。

常見問題

Bjarne Stroustrup 的工程哲學是什麼?

Stroustrup 的信念是:你永遠不該被迫在富表達力的高階程式碼與貼近硬體的效能之間二選一。他以 零開銷原則 打造 C++:一項語言功能在你不使用它時必須毫無代價,而當你使用它時,它編譯出的程式碼必須不遜於你為那項工作親手寫出的成果。1他將這份信念,與信任程式設計師(C++ 刻意地維持多典範)、直接對應硬體、透過 RAII 進行確定性的資源管理,以及把語言穩定為一個開放的 ISO 標準而非據為己有,結合在一起。467

零開銷原則是什麼?

這是 Stroustrup 分成兩部分陳述的規則:「沒用到的,就不必付出代價(無論時間或空間);更進一步:用到的,你也無法手寫得更好。」1前半句意味著一項未被使用的功能不施加任何執行期或記憶體成本;後半句意味著一項你確實使用的功能,產生的程式碼至少要和一位稱職的程式設計師親手寫出的一樣好。它是每一項 C++ 功能都必須通過的關口。那兩項扭曲了它的功能——執行期型別資訊(RTTI)與例外處理——恰恰就是多數編譯器讓你能夠關閉的那兩項。6正是這條原則,讓一個像智慧指標或迭代器這樣的 C++ 抽象,能編譯成與那個原始、手工調校的版本相同的機器碼。

RAII 是什麼,Stroustrup 為何發明它?

RAII——資源取得即初始化(Resource Acquisition Is Initialization)——是由 Stroustrup 命名的 C++ 模式,它把一項資源的生命週期綁定在一個堆疊物件的生命週期上:你在物件的建構子中取得資源,在它的解構子中釋放資源。7由於 C++ 保證一個堆疊物件的解構子會在它離開作用域的那一刻執行——無論函式是正常返回,或是有例外將堆疊回溯展開——像記憶體、檔案、鎖與 socket 這類資源都會被確定性地釋放,發生在一個已知的瞬間,不需垃圾回收,也沒有任何東西需要手動釋放。它讓你以零開銷得到自動、例外安全的清理,而 Rust 後來幾乎原封不動地把它採納為所有權與 Drop7

Stroustrup 為何創造 C++,它又從何而來?

在撰寫他那篇探討分散式系統的劍橋博士論文時(1979 年完成),Stroustrup 用 Simula 打造了一個模擬器,深愛它的類別抽象,卻發現它在大規模下太慢;他改用 BCPL 重寫,後者很快,卻太低階,無法用來打造任何龐大的東西。2兩種取捨他都無法接受,於是在 貝爾實驗室——面對一個跨網路分析 UNIX 核心的問題時——他著手把 Simula 的組織能力加進 C,同時不失去 C 的速度。他把它稱為 「C with Classes」(1979 年);他的同事 Rick Mascitti 在 1983 年提議了 C++ 這個名字。452018 年,美國國家工程院授予他 Charles Stark Draper 獎——美國工程界的最高榮譽,一項常被稱為工程界諾貝爾獎的 50 萬美元獎項——以表彰他在設計與實作 C++ 程式語言上的成就。9


資料來源


  1. “What is the zero-overhead principle?” Standard C++ Foundation FAQ (isocpp.org),與 Stroustrup 共同維護。「What you don’t use, you don’t pay for (in time or space) and further: What you do use, you couldn’t hand code any better.」另見 Bjarne Stroustrup, “C++ – an Invisible Foundation of Everything,” Overload (ACCU), 2021。 

  2. “C++,” Wikipedia。在劍橋撰寫博士論文期間,Stroustrup 使用 Simula,卻發現它在大規模下太慢,於是用 BCPL 重寫他的模擬器;他的結論是 Simula 具備有助於大型軟體開發的特性,但太慢而不堪實用,而 BCPL 雖快卻太低階。第一手記述:Bjarne Stroustrup, “A History of C++: 1979–1991,” HOPL-II(他本人的論文)。 

  3. Bjarne Stroustrup, “Quotes,” stroustrup.com(他本人的網站)。「I like my code to be elegant and efficient. The logic should be straightforward to make it hard for bugs to hide, the dependencies minimal to ease maintenance…」以及「Code should [be] elegant and efficient; I hate to have to choose between those.」 

  4. “Bjarne Stroustrup,” Wikipedia。1950 年 12 月 30 日生於丹麥奧胡斯;1979 年取得劍橋大學博士學位,論文為「Communication and control in distributed computer systems」;曾領導貝爾實驗室的大規模程式設計研究部門;《The C++ Programming Language》(1985);C++ 標準委員會的創始成員(1989 年起 ANSI、1991 年起 ISO),主持演進工作小組;後任教於德州農工大學(2002–2014)、摩根士丹利(2014–2022),自 2022 年起為哥倫比亞大學正教授。另見 Stroustrup 於 stroustrup.com 上的 Bio/FAQ。 

  5. Bjarne Stroustrup, “Bjarne Stroustrup’s FAQ,” stroustrup.com。「I wanted to write efficient systems programs in the styles encouraged by Simula67… The specific tasks that caused me to start designing and implementing C++ (initially called ‘C with Classes’) had to do with distributing operating system facilities across a network.」關於 1983 年由 Rick Mascitti 改名一事:“C++,” Wikipedia。 

  6. “Zero-overhead principle,” cppreference.com。陳述了 Stroustrup 所定義的這條原則,並指出不遵循它的那兩項功能——執行期型別辨識與例外處理——以及編譯器通常會內建開關來關閉它們。關於多典範設計,以及對 Rust「zero-cost abstractions」的影響:“Zero-overhead principle” 與 Rust 關於 zero-cost abstractions 的文件。 

  7. “Resource acquisition is initialization,” Wikipedia。RAII 一詞由 Stroustrup 所創;這項技術是在 1984–1989 年間為 C++ 的例外安全資源管理而開發,主要由 Stroustrup 與 Andrew Koenig 完成,並在他 1994 年的著作《The Design and Evolution of C++》中引介。資源釋放發生在解構子中;C++ 保證具自動儲存期的物件會在其所屬作用域結束時,以建構的反向順序被銷毀,從而提供確定性、例外安全的清理。 

  8. Bjarne Stroustrup, The Design and Evolution of C++ (1994),設計規則,如 “(Re)affirm design principles for future C++ evolution,” ISO C++ 委員會論文 P3466R0 中所討論。「Leave no room for a lower-level language below C++ (except assembler)」——其推論在於,倘若能用某個更低階的語言寫出更有效率的程式碼,那個語言便會成為眾人首選的系統語言;為保持可行,C++ 必須保留 C 對硬體的直接存取、對資料佈局的掌控,以及一對一對應到硬體的基本型別。 

  9. “Bjarne Stroustrup receives Draper Prize, engineering’s top U.S. honor,” Standard C++, 2018:2018 年的 Charles Stark Draper 獎,「a $500,000 biannual award… considered the Nobel Prize of engineering」,授予 Stroustrup「for his work designing and implementing the C++ programming language」。美國國家工程院本身的表彰詞則措辭為「for conceptualizing and developing the C++ programming language」(NAE, “Computer Science Pioneer Bjarne Stroustrup to Receive the 2018 Charles Stark Draper Prize for Engineering,” 經由 EurekAlert, 2018)。他也於 2017 年因在 C++ 上的開創性工作獲頒 IET 法拉第獎章(Faraday Medal)。 

相關文章

工程哲學:Barbara Liskov,契約即型別

Barbara Liskov讓資料抽象成為程式設計的基本要素:型別就是它所信守的契約,而子型別必須兌現其父型別所許下的每一個承諾。

3 分鐘閱讀

工程哲學:John Carmack,把效能當成一種道德工藝

John Carmack 把效能視為一道道德課題。剝除到只剩快速、簡潔的核心,把問題理解到根基,將它交付出去,並把過程攤開給人看。

3 分鐘閱讀

The Shader Gallery That Lied: Debugging 216 WebGL Presets

A user said the shader playground looked broken. Pixel-readback testing found 30 dead presets, 11 that never compiled, a…

11 分鐘閱讀