C 標準規定: 在兩個序列點之間,對同一標量對象進行多次修改,或修改後又讀取其值(非為了確定新值),行為未定義 (Undefined Behavior, UB)。
int i = 5;
int p = (i++) + (i++); // UB:i 被修改兩次
int q = (++i) + (++i); // UB:i 被修改兩次
int r = i++ + ++i; // UB:修改後又讀取
int s = a[i++] = i; // UB:修改 i 後又讀取 i 決定存儲位置
舊版 MSVC (VC6.0) 採用特定求值順序與副作用應用策略:
i++ 表達式值為舊值,副作用 (遞增) 延遲到語句結束統一應用++i 立即應用副作用,表達式值為新值++i 會累積遞增次數但這是編譯器特定實現,非標準保證! 換編譯器 (GCC, Clang, 新版 MSVC)、優化等級、周圍代碼微調,結果皆可能改變。
| 表達式 | 求值順序保證 |
|---|---|
a + b | C++17 前:未規定;C++17:仍未規定 (但運算元副作用不交織) |
a = b | 右運算元先於左運算元求值 (C++17 明確) |
f(a, b, c) | 參數求值順序未規定 (C++17: 參數初始化不交織,但順序仍未指定) |
a[b] = c | b、c 求值順序未規定 |
a && b, a || b, a ? b : c, a , b | 嚴格左到右,帶短路/序列點 |
int i = 5;
printf("%d %d\n", i++, i++); // GCC: 5 6 | Clang: 5 6 | MSVC: 6 5 (參數壓棧順序不同)
int j = 5;
printf("%d\n", (++j) + (++j)); // GCC: 14 | Clang: 14 | MSVC: 14 (巧合一致,不可依賴)
| 想要效果 | 錯誤寫法 | 正確寫法 |
|---|---|---|
| 連續遞增兩次後求值 | x = ++i + ++i; | i += 2; x = i + i; |
| 用舊值計算後遞增兩次 | x = i++ + i++; | x = i + i; i += 2; |
| 遞增後用新值 | x = ++i * ++i; | ++i; x = i * i; |
| 數組索引與值同步 | a[i++] = i; | int idx = i++; a[idx] = i; |
-Wall -Wextra -Wsequence-point (GCC/Clang)-fsanitize=undefined 可捕獲部份 UB-Werror)。