不知你在寫code時是否遇到這樣的問題?int i = 3; int x = (++i) + (++i) + (++i); 問x值為多少?進行各種理論分析,并在編譯器上實踐,然而可能發現最終的結果是不正確的,也是不穩定的,不同的編譯器可能會產生不同的結果。這讓人很頭疼。結果到底是啥呢?對于此題的答案,一句話,Theresult is undefined! 詳細解釋待我慢慢說來。
大家知道,通常而言,我們寫的計算機程序都是從上到下,從左到右依次執行。然而,我只是說通常,因為在編譯的過程中,compiler并不僅僅是把source code翻譯成binary code就算了,這個過程里面可能還會對代碼進行優化,這種優化可能帶來的結果是:代碼或者表達式evaluation的順序可能發生變化。這可是一個非常嚴重的問題,當某個表達式帶有side-effect(比如改變了一個變量的值),那么它的執行順序直接影響到了程序執行的結果。
為了保證程序執行具有確定性的結果,C++標準引入Sequence Point這個概念,按照ISO/IEC的定義:
At certain specified points in the execution sequence called sequence points. All side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place.
簡而言之,Sequence Point就是這么一個位置,在它之前所有的side effect已經發生,在它之后的所有side effect仍未開始,而兩個Sequence Point之間所有的表達式或者代碼執行的順序是未定義的!
而C++標準又進一步規定了Sequence Point出現的5種情況:
1、At the end of a full expression
在一個完整的表達式末尾是Sequence Point,所謂完整的表達式是指這個表達式不是另外一個表達式的一部分。所以如果有f(); g();這樣兩條語句,f()和g()是兩個完整的表達式,f()的Side Effect必定在g()之前發生。
2、After the evaluation of all function arguments in a function call and before execution of any expressions in the function body
調用一個函數時,在所有準備工作做完之后、函數調用開始之前是Sequence Point。比如調用foo(f(), g())時,foo、f()、g()這三個表達式哪個先求值哪個后求值是Unspecified,但是必須都求值完了才能做最后的函數調用,所以f()和g()的Side Effect按什么順序發生不一定,但必定在這些Side Effect全部作用完之后才開始調用foo函數。
3、After copying of a returned value and before execution of any expressions outside the function
函數即將返回時是Sequence Point,因為函數返回時必然會結束掉一個完整的表達式。
4、After evaluation of the first expression in a&&b, a||b, a?b:c, or a,b
條件運算符?:、逗號運算符、邏輯與&&、邏輯或||的第一個操作數求值之后是Sequence Point。如條件運算符和逗號運算符,條件運算符要根據表達式1的值是否為真決定下一步求表達式2還是表達式3的值,如果決定求表達式2的值,表達式3就不會被求值了,反之也一樣,逗號運算符也是這樣,表達式1求值結束才繼續求表達式2的值。
5、After the initialization of each base and member in the constructor initialization list
在一個完整的聲明末尾是Sequence Point,所謂完整的聲明是指這個聲明不是另外一個聲明的一部分。比如聲明int a[10], b[20];,在a[10]末尾是Sequence Point,在b[20]末尾也是。
經過以上說明,大家已有所了解,現在回到我們的題目:int x = (++i) + (++i) + (++i); 整個的語句里面,只有1個Sequence Point,也就是語句的結束點,對于右邊表達式的計算順序沒有任何的規定,顯然,各種編譯器都可以按照他們覺得“舒服”的方式來進行計算,這樣的代碼,如果只要求在特定的平臺或者編譯器運行,那么帶來的可能只是可讀性差的問題,但如果考慮跨平臺或者編譯器的情況,那么就是完完全全的錯誤!
另外,需要特別注意的是,對于賦值號(assignment operator),C++也沒有把它定義成Sequence Point,也就說這樣的語句:buffer[i] = i++;同樣是undefined的,因為,對于等號左右兩邊的表達式運算順序,你并不能有任何的假定。