自增(++)和自減(--)是 C 語言中最基礎卻又最容易產生誤解的運算符。無數初學者(甚至資深開發者)都在 a = i++ + ++i 這類表達式前栽過跟頭。本文將通過語法規則、經典案例、彙編視角三個維度,徹底拆解其背後的求值邏輯與副作用陷阱。
| 形式 | 名稱 | 核心規則 |
|---|---|---|
++i / --i | 前置 | 先增/減,再參與表達式運算,結果為新值 |
i++ / i-- | 後置 | 先參與表達式運算(用舊值),運算結束後再增/減 |
關鍵點:操作數必須是可修改的左值(變量),不能是常量或表達式結果。
int i = 3, j = 4; i++; ++j; // 結果:i=4, j=5 (無歧義) int a = i++; // a=3, i=4 (先賦值,後自增) int b = ++j; // b=5, j=5 (先自增,後賦值)
int i = 3, j = 4; int a = i++ + i++; // a = 3 + 3 = 6,隨後 i 變為 5 int b = ++j + ++j; // b = 5 + 5 = 10?錯!實際為 12 (見下文)
後置:整個表達式求值完成後,統一執行副作用(自增)。
前置:涉及運算符結合性與求值順序。
c = (a++)+(a++)+(a++)int a=5, b=5; int c = (a++) + (a++) + (a++); // c = 5+5+5 = 15,最終 a=8 int d = (++b) + (++b) + (++b); // d = ?
輸出結果:a=8, b=8, c=15, d=19。
為什麼 d=19 而不是 18 或 15?
+ 運算符具有左結合性,表達式 ++b + ++b + ++b 實際等價於 ((++b + ++b) + ++b)。
C 標準規定:在前一個序列點到下一個序列點之間,對同一變量的修改最多只能發生一次,且讀取值必須用於計算該修改的新值。但在單個表達式內,多個前置自增的副作用完成順序由編譯器實現定義(Unspecified Behavior),VC 編譯器選擇「從左到右依次完成副作用並參與運算」。
; d = (++b) + (++b) + (++b) 初始 b=4 (存儲於 ebp-8) mov ecx, [ebp-8] ; 讀取 b add ecx, 1 ; 第一次 ++b -> 5 mov [ebp-8], ecx ; 寫回 b mov edx, [ebp-8] ; 讀取 b (已是 5) add edx, 1 ; 第二次 ++b -> 6 mov [ebp-8], edx ; 寫回 b mov eax, [ebp-8] ; 讀取 b (已是 6) add eax, [ebp-8] ; eax = 6+6 = 12 (前兩項和) mov ecx, [ebp-8] ; 讀取 b (6) add ecx, 1 ; 第三次 ++b -> 7 mov [ebp-8], ecx ; 寫回 b add eax, [ebp-8] ; eax = 12 + 7 = 19 mov [ebp-10h], eax ; 賦值給 d
同理,i++ + i++ + i++ 的彙編顯示:先用原值 3 計算三次相加得 9,最後再連續執行三次 inc 指令將 i 變為 6。
int i=1, j=1; int a = i++ + i++ + i++ + i++ + i++ + i++ + i++; // 7個 int b = ++j + ++j + ++j + ++j + ++j + ++j + ++j; // 7個 // 結果:a=7 (1*7), i=8 // b=36, j=8 // b 計算過程:(2+2)=4, +3=7, +4=11, +5=16, +6=22, +7=29, +8=36? 實際編譯器優化可能不同,但遵循左結合逐步副作用原理。
a = i++ + ++i 拆為 a = i + (i+1); i += 2; 等清晰邏輯。-Wsequence-point,MSVC 加 /Wall 可捕獲大多數此類問題。總結:自增自減本質是「讀取-修改-寫入」的原子操作封裝。掌握運算符優先級、結合性、序列點規則,配合彙編視角分析,方能在複雜表達式中游刃有餘。實際開發中,「清晰可維護」永遠優於「炫技一行流」。