匿名
此帳號疑似異常
官方正在進行身份確認

#分享 估值順序 (order of evaluation)

2020年5月29日 18:36
前情提要
0. 前綴遞增運算子 (prefix increment operator) 的優先權高於 + - × / % 運算子 [1],那應該要先做才對? a * 9 + (b + 7 % 2) - 20 * 7 % (b % 5) - ++a a * 9 + (b + 7 % 2) - 20 * 7 % (b % 5) - 9 a * 9 + (12 + 7 % 2) - 20 * 7 % (12 % 5) - 9 a * 9 + (12 + 1) - 20 * 7 % (12 % 5) - 9 a * 9 + (13) - 20 * 7 % (12 % 5) - 9 a * 9 + (13) - 20 * 7 % (2) - 9 9 * 9 + (13) - 20 * 7 % (2) - 9 81 + (13) - 20 * 7 % (2) - 9 81 + (13) - 140 % (2) - 9 81 + (13) - 0 - 9 85 所以答案應該是 85 還是 76? 都不是,這行運算式是未定義行為, 而其結果值是為未定義值。 1. 估值順序 (order of evaluation)
實際執行結果,印出 14 這符合預期, 但函式執行的順序並不是 B() -> C() -> A() 而是 A() -> B() -> C() 我們認為函式 A() 應該要最後才被執行, 但它卻是第一個被執行的函式。 為什麽這樣子?(妳拉著我說妳有些猶豫) 原來,在 C 語言中,估值順序這是一種未指定行為 (unspecified behavior),也就是說,在上方的程式中編譯器可以採取任意的估值順序。 A() -> B() -> C() A() -> C() -> B() B() -> A() -> C() B() -> C() -> A() C() -> A() -> B() C() -> B() -> A() """"" [2] Order of evaluation of the operands of any C operator, including the order of evaluation of function arguments in a function-call expression, and the order of evaluation of the subexpressions within any expression is unspecified (except where noted below). The compiler will evaluate them in any order, and may choose another order when the same expression is evaluated again. """"" """"" [3] unspecified behavior - two or more behaviors are permitted and the implementation is not required to document the effects of each behavior. For example, order of evaluation, whether identical string literals are distinct, etc. Each unspecified behavior results in one of a set of valid results and may produce a different result when repeated in the same program. """"" 至此,我們已經可以了解到估值順序與結果計算,這是兩件事情。 請注意,在對結果進行計算時,還是會遵循結合律以及運算子優先權。
因為加法運算子是由左向右結合 所以 a + b + c 等同於 (a + b) + c a, b, c 誰先被估值,這是未指定行為。 所以以下順序是其中一種可能 (a + b) + c (a + b) + 4 // 先對 c 進行估值 (2 + b) + 4 // 再對 a 進行估值 (2 + 3) + 4 // 最後對 b 進行估值 不過,在對結果進行計算時, 還是會先求出 (2 + 3) = 5 接著才會執行 5 + 4 = 9 請問下方程式會印出何值?
2. 回到原 PO 的問題 a * 9 + (b + 7 % 2) - 20 * 7 % (b % 5) - ++a 這段運算式有問題的地方在於:「是 a * 9 先做?還是 ++a 先做?」 我們現在已經知道答案是:「不一定」,眼見為憑。
同一份程式碼 gcc/clang 因採取不一樣的估值順序,而得到了不一樣的結果。 先對 ++a 進行估值的話,會得到 2 並將 a 的值更新為 2 此時再對 a 進行估值的話就會得到 2 所以 2 - 2 = 0 先對 a 進行估值的話,會得到 1 此時再對 ++a 進行估值的話會得到 2 並將 a 的值更新為 2 所以 1 - 2 = -1 接著來看看原例吧,假如我把 - ++a 調到運算式的最前頭的話,那會輸出什麽呢?
我們再一次看到,同一份程式碼 gcc/clang 因採取不一樣的估值順序,而得到了不一樣的結果。 gcc 選擇先對 a * 9 進行估值,再對 ++a 進行估值,而 clang 則恰好相反。 此外 gcc/clang 都很友善地提醒(警告)我,當前的行為有可能是未定義的,而事實也確實如此,且看 C 語言規格書 6.5.2 """"" [4] 6.5 Expressions 2. If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. """"" 我引用這段話只是為了讓論述變得嚴謹而已,我不打算在這裡向妳解釋 sequence point, sequenced before, and side effect 它們分別是什麽,因為這些名詞對新手來說很不友善(如果妳很感興趣的話,我把它們放在補充資料裡面了),取而代之的是,我從估值順序是未指定行為出發,來向妳解釋為何這段程式碼它是未定義行為,希望會對妳有幫助。 3. 很規律地使用遞增/遞減運算子 (e.g. for loop) 除非你想挑戰你自己、想挑戰你的編譯器,否則你不應該寫出以下程式碼。
4. 參考資料(請使用網頁版瀏覽) [1]:
[2]:
[3]:
[4]:
5. 補充資料(請使用網頁版瀏覽) sequence point:
sequenced before:
side effect:
9
留言 2
文章資訊
Logo
每天有 7 則貼文
共 2 則留言
約翰霍普金斯大學
請問最開頭 a * 9 那邊的a 是不是應該用 8 去帶?
匿名
此帳號疑似異常
官方正在進行身份確認
B1 為什麽應該要代入 8 呢? 如同我在第 2 點所提到的, 若 ++a 在 a * 9 之前先被估值, 則當妳再對 a 進行估值時, a 的內容值就已經是 9 了。 如果妳對以上論述感到困惑的話,那還請妳點擊下方連結, 以充份理解遞增/遞減運算子的定義、行為以及範例。