在 C++ 程式設計中,運算子優先順序與副作用的評估順序常是初學者困惑的來源。今天在計算技術基礎課上,有同學問道:當 a = 0 時,表達式 (++a) + (++a) + (++a) 為什麼結果是 7 而不是 6?這個問題看似簡單,實則觸及了 C++ 標準中關於「未定義行為」與「運算順序」的核心規則。
考慮以下程式碼:
int main() {
int a = 0;
int b = 0;
b = (++a) + (++a) + (++a);
return 0;
}
為了探究其底層執行邏輯,我們對編譯後的程式進行反組合碼分析(以 x86 架構為例):
[01]: mov [ebp-04], 0 ; a = 0
[02]: mov [ebp-08], 0 ; b = 0
[03]: mov eax, [ebp-04] ; 載入 a (0)
[04]: add eax, 1 ; ++a → 1
[05]: mov [ebp-04], eax ; 存回 a (1)
[06]: mov ecx, [ebp-04] ; 載入 a (1)
[07]: add ecx, 1 ; ++a → 2
[08]: mov [ebp-04], ecx ; 存回 a (2)
[09]: mov edx, [ebp-04] ; 載入 a (2)
[10]: add edx, [ebp-04] ; edx = 2 + 2 = 4 (前兩個 ++a 相加)
[11]: mov eax, [ebp-04] ; 載入 a (2)
[12]: add eax, 1 ; ++a → 3
[13]: mov [ebp-04], eax ; 存回 a (3)
[14]: add edx, [ebp-04] ; edx = 4 + 3 = 7
[15]: mov [ebp-08], edx ; b = 7
從反組合碼可見,編譯器採用了「先計算所有前綴遞增,再執行加法」的策略:
++a:a 從 0 變 1++a:a 從 1 變 2++a:a 從 2 變 3關鍵點來了:根據 C++ 標準(C++17 之前),在同一個序列點內對同一標量物件多次修改,屬於未定義行為。雖然特定編譯器(如 MSVC、GCC 在特定優化等級下)可能產生 7,但標準並不保證此結果。不同編譯器、不同優化等級、甚至同編譯器不同版本,都可能產生不同結果(如 6、7、9 等)。
C++17 引入了更嚴格的運算順序規則:a + b + c 中,a、b、c 的評估順序仍未規定,但運算子的副作用完成順序有了更明確限制。即便如此,(++a)+(++a)+(++a) 仍屬未定義行為,因為 a 在單一表達式中被修改多次。
int a = 0;
++a;
++a;
++a;
int b = a + a + a; // 明確、可攜、無歧義
-Wall -Wextra -Wsequence-point),讓編譯器幫你捕捉此類問題。雖然反組合碼揭示了特定編譯器產生 7 的微觀機制,但切勿依賴此行為。C++ 之所以規定此為未定義行為,正是為了給編譯器最佳化留出空間。寫出清晰、無副作用衝突的程式碼,才是專業工程師的基本功。