C++26 收掉四類 UB:改成編譯錯誤或有定義行為
Sandor Dargo(isocpp.org 於 2026-10-09 轉載) · 2026-10-09
C++26 把四類原本「編譯過了、跑起來才知道」的未定義行為(UB),改成編譯期錯誤、有定義的錯誤行為,或可在執行期攔截的契約違反。受影響的不是新語法,而是你現有程式碼的語意:升級編譯器與標準版本後,有些舊碼會直接編譯失敗,有些則從「隨機壞掉」變成「可診斷」。
這四項分別是 P3144R2、P2795R5、P2748R5 與 P3471R4。
原本的問題
UB 的麻煩在於編譯器不必告訴你。對不完整型別(incomplete type)做 delete、讀取未初始化的區域變數、回傳綁到暫時物件的參考,這些寫法在舊標準下都是 UB,程式可能正常、可能損毀,換個優化等級結果就不同。
另一類問題在標準函式庫:vector 越界存取、對空的 optional 解參考,違反前置條件時標準只說「行為未定義」,沒有統一的方式要求實作去檢查。
核心改動
一、對不完整型別 delete 變成 ill-formed。舊標準只在「完整類別的解構函式是 trivial、且沒有類別專屬 deallocation function」時才不算 UB;P3144R2 之後直接編譯失敗。
| C++23 以前 | C++26 | |
|---|---|---|
| delete 不完整型別指標 | UB(特定條件除外) | ill-formed,編譯器拒絕 |
| 讀取未初始化區域變數 | UB | 錯誤行為(erroneous behaviour) |
| 回傳參考綁到暫時物件 | UB | ill-formed |
| 函式庫前置條件違反 | UB | 硬化實作中為契約違反 |
struct Widget; // 此編譯單元看不到定義
void destroy(Widget* p) {
delete p; // C++26: ill-formed
}修法是在 delete 的位置讓型別完整,通常就是多 #include 一個標頭。文章指出主流編譯器過去就會對這種寫法發警告,所以多數程式碼早該乾淨。
二、未初始化讀取變成「錯誤行為」。P2795R5(日期 2024-03-22)新增這個行為類別,定義為「well-defined behavior that the implementation is recommended to diagnose」。它仍然是程式碼的錯,但不再有安全風險,物件會拿到由實作決定的值。
void f(int);
void g() {
int x [[indeterminate]], y;
f(y); // erroneous behavior
f(x); // undefined behavior
}真的需要保留未初始化儲存(例如大型暫存緩衝區)時,用 [[indeterminate]] 明確標註,此時讀取仍是 UB。這項改動只需重新編譯,但效能敏感的程式要檢查哪些緩衝區該加屬性。
其餘兩項
三、回傳參考綁暫時物件變成 ill-formed。P2748R5 讓 return 把回傳的參考綁到暫時物件時直接報錯。文章的例子包括從 42 回傳 auto&&,以及回傳 const double& 卻從 static int 隱含轉換出暫時物件;這兩種以前都編得過,現在不行。
四、函式庫硬化。P3471R4(日期 2025-02-14)讓部分前置條件違反在「硬化實作」中成為契約違反,且不能用 ignore 語意關掉。是否為硬化實作、怎麼開啟(編譯旗標或巨集)都由實作自行決定;非硬化實作下仍是 UB。
- 涵蓋
span、basic_string_view、序列容器的front/back/a[n]、basic_string - 也涵蓋
mdspan、bitset、valarray、optional的operator->/operator*,以及expected - 論文引用 Google 的部署數據:效能影響低至 0.3%,找出超過 1000 個 bug
影響範圍
會直接編譯失敗的是前兩類 ill-formed 變更:用前置宣告加 unique_ptr 或裸指標、卻在沒看到定義的檔案裡銷毀物件的程式碼,以及回傳參考時依賴暫時物件轉換的輔助函式。升級到 C++26 的 CI 要先看這兩種錯誤。
函式庫硬化不改程式碼,但跑 release 的服務要決定是否開啟:它把越界從「偶發損毀」變成「確定終止」,代價是論文引用的那組低至 0.3% 的數字是否適用於你的負載,論文未保證。混用硬化與非硬化的編譯單元,論文規定為 unspecified,連結多個函式庫時要統一旗標。