C 語言邏輯與(&&)和邏輯或(||)具備短路求值特性:若左操作數已能決定整體結果,右操作數不再求值。
// 避免除零:d==0 時短路,不執行 n/d
if (d != 0 && n / d > 0) { /* ... */ }
// 避免空指針解引用:p==NULL 時短路,不執行 *p
if (p == NULL || *p == '\0') { /* ... */ }
這兩個例子若無短路特性,將導致運行時崩潰。短路求值是 C 語言標準保證的行為,可放心用於防禦性檢查。
C 標準未規定函數實參的求值順序,編譯器可自由選擇(常見為右到左,但不可依賴)。
int i = 3;
printf("%d, %d\n", i, i++);
// 可能輸出:4, 3 (右到左求值)
// 也可能輸出:3, 3 (左到右求值)
// 甚至其他結果 —— 未定義行為!
結論:絕不在同一函數調用的多個實參中對同一變量進行修改或依賴修改後的值。
++i(前綴)返回遞增後的值;i++(後綴)返回遞增前的值。但副作用(內存寫入)何時完成由序列點決定。
int i = 3;
int j = (++i) + (++i) + (++i);
// 未定義行為!i 在兩個序列點之間被修改多次
// GCC 輸出:j=16, i=6 (但不可依賴)
int i = 1, j = 1, k;
i = (i++) + (++i); // UB: i 既作為左值賦值,又在右邊被修改兩次
k = (j++) + (++j); // UB
// GCC 輸出:i=5, j=3, k=4 (僅供參考)
C99/C11 標準(6.5 段):
在前後兩個序列點之間,某個標量對象的值至多被修改一次;若被修改,則前一次值僅用於確定要存儲的新值。
違反此規則即產生未定義行為:程序可能崩潰、輸出錯誤結果、甚至看似正常運行但在不同編譯器/優化級別下表現不同。
// 不安全
a[i++] = b[i++];
// 安全
int idx = i++;
a[idx] = b[i];
-Wall -Wextra -Wsequence-point (GCC/Clang) 可捕獲大多數序列點違規。&&、||、?:、,)引入序列點,左操作數副作用確保在右操作數求值前完成。| 代碼模式 | 行為分類 | 是否可移植 | 建議 |
|---|---|---|---|
d != 0 && n/d | 短路求值 (標準保證) | ✅ 是 | 推薦使用 |
f(i++, i++) | 參數求值順序未指定 + 多重修改 | ❌ 否 | 嚴禁 |
i = i++ + ++i | 無序列點多重修改 | ❌ 否 (UB) | 嚴禁 |
i = (i++) + (++i) | 同上,括號不改變序列點 | ❌ 否 (UB) | 嚴禁 |
a = b++ + c++ | 修改不同對象,合法 | ✅ 是 | 允許 |
C 語言給予開發者極大自由,也要求極高自律。理解序列點、未指定行為、未定義行為的區別,避開副作用陷阱,是從「能跑通」進階到「寫得對、跑得穩」的必經之路。