Betteridge 說「不」,而且對於一般開發者經驗來說,這個答案大部分是正確的。(另外,你完全說對了!這裡有一個破折號,讓你明白我是用 ChatGPT 來協助撰寫這篇文章的。)
我的看法是,有一個電腦科學的答案和一個工程學的答案。電腦科學的答案是:編譯器是其完整輸入狀態的確定性函數。工程學的答案是:大多數實際的建置過程並未控制完整的輸入狀態,因此輸出會有所漂移。
我在 2000 年代曾在 Ksplice 工作,當時我們會在記憶體中修補執行中的 Linux 核心,以便在不重新開機的情況下進行安全性更新。閱讀崩潰核心的 objdump 輸出並非日常例行公事,但我必須經常這麼做,以至於「編譯器輸出對比原始碼意圖」不再是理論上的問題。
大多數團隊只會保持原始碼和可能的編譯旗標恆定,然後將其他所有東西稱為「雜訊」。這種「雜訊」正是導致不可重現性的根源。
我在 2000 年代於 Ksplice 經歷了艱難的學習。我們透過比較舊版與新版編譯輸出的差異,並將熱補丁縫補到即時核心記憶體中,來產生無需重新開機的 Linux 核心更新。大多數差異都能乾淨地對應到變更的 C 程式碼。但有時,它們會因為非語義上的原始碼變更而爆炸:暫存器分配的差異、處理階段行為的改變、區段/佈局的變更。意圖相同,但機器碼不同。
如果你想要一個具體的歷史證據,GCC bug 18574 有一個 gcc-bugs 的討論串,提到了指標雜湊不穩定性影響了遍歷順序和 SSA 合併。
評論者在此點上是正確的:編譯器應保留語義。對於行為定義明確的程式,輸出應該在觀察上等同於原始碼語言的抽象機器。
這意味著指令順序、暫存器選擇、內嵌策略和區塊佈局都是可以變動的,只要外部可見的行為保持不變。實際上,「可見行為」指的是 I/O 效果、volatile 存取、原子同步保證和定義的回傳值等,而不是位元對位元的指令識別。
ASLR 註記:ASLR 並不直接隨機化發出的二進位檔。它隨機化處理器記憶體的佈局。但如果編譯器處理階段的行為取決於指標識別/順序,ASLR 可能會間接擾亂結果。
所以,「編譯器是確定性的」在定理意義上通常是真的,但在操作意義上卻是假的。即使有可重現的構件,Ken Thompson 的「關於信任的思考」仍然適用。同時也要記住,編譯器並非新技術:Grace Hopper 的 A-0 系統可追溯到 1952 年在 UNIVAC 上。ChatGPT 只有 4 年歷史,而編譯器則有 74 年?
Debian 和更廣泛的可重現建置(Reproducible Builds)計畫(約 2013 年起)將此推向主流:相同的原始碼 + 相同的建置指令應該產生位元對位元完全相同的構件。
我們現在有這個嗎?在許多生態系統中,大部分是的。但這需要編譯器、連結器、封裝和建置系統多年來非常刻意的努力。我們是透過艱辛地處理奇怪的邊緣案例才達到這一點,而不是透過揮揮手就宣稱純粹。
這現在又浮現為「如果 LLM 是不確定的,那麼 vibecoding 是否健全?」的問題。再次強調:你想要電腦科學的答案,還是工程學的答案?
我們已經解決,也尚未解決 LLM 的停機問題。我們在形式意義上遠未解決停機問題。但就實際用途而言,如果我寫了一個 for 迴圈並弄錯了條件,LLM 可以查看我的程式碼,告訴我我太笨了,然後它可以幫我修正。
工程學從未依賴於完全確定的智慧。它依賴於受控的介面、測試預言機、可重現的管道和可觀察性。我對 AI 的信心足夠讓我日常使用 comma.ai,但我仍然希望在生成程式碼周圍有確定的驗證閘。我的女朋友更喜歡我讓它開車,因為它比我更平穩、更不易失控,這提醒我們「機率性系統」和「操作上更好的結果」可以共存。
LLM 輔助編碼也是同樣的模式:
電腦科學的答案:不確定性是可怕的。工程學的答案:控制邊界條件、驗證輸出、發布。
是的,這部分論證是關乎生存的:我們大多數人仍在為房租打拼,而不是從事哲學。所以我們使用能推動工作前進的工具,然後建立我們需要的護欄。
部落格部落格部落格:隨機筆記/想法/等