簡介
在C、C++等允許表達式有副作用的語言中,通常都規定了順序點,順序點就是表達式中的副作用的最晚生效時刻,或指程式執行中關鍵的一點。在這個順序點之前,所有的副作用的計算工作都必須完成,可以說順序點是前一階段計算的分水嶺 。在程式執行中,可以存在一系列的順序點,一旦執行到一個順序點,此前的副作用都必須生效。但是在順序點之間並沒有任何保證。在C++11中,順序點概念已經被這種方法取代:直接指出一個求值是在另一個求值之前,或者兩個求值是無順序的。無順序的求值可以重疊進行。
表達式
表達式亦稱表示式、運算式或數學表達式,在數學領域中是一些符號依據上下文的規則,有限而定義良好的組合。數學符號可用於標定數字(常量)、變數、操作、函式、括弧、標點符號和分組,幫助確定操作順序以及有其它考量的邏輯語法。表達式有兩種功能:
表達式一定會產生一個值
表達式可能會產生“副作用”(side effect)。
•比如,i++的返回值是i,副作用是導致i增加1。
•比如,++i的返回值是i+1,副作用也是導致i增加1。
表達式中的副作用何時生效以及運算對象的求值順序,會影響到表達式的值。比如:
如果“+”的兩個運算對象,從左到右計算:
•如果後綴遞增運算符的副作用,在計算第二個運算元“a”時,已經生效,那么b的值是9;
•如果後綴遞增運算符的副作用,在進入到“+”運算之時,才生效,那么b的值是8。
如果“+”的兩個運算對象,從右到左計算,b的值都是8。
副作用
在C++標準指出,副作用是訪問一個由可變的左值(volatile lvalue)指派的對象(basic.lval),修改一個對象,調用庫I/O函式,或者調用函式等所有這些能夠改變執行環境的狀態的操作都是副作用。
順序點的位置
在C與C++中,順序點在下述位置出現:(C++的重載操作符的行為類似於函式)
&&(邏輯與)、||(邏輯或)、逗號運算符的左運算元與右運算元求值之間(前兩者是短路求值的一部分)。例如,表達式*p++ != 0 && *q++ != 0,子表達式*p++ != 0的副作用都會在試圖訪問q之前完成。
三元條件運算符的第一個運算元之後,第二或第三運算元之前。例如,表達式a = (*p++) ? (*p++) : 0在第一個*p++之後存在順序點,因而在第二個*p++求值之前已經做完一次自增。
完整表達式結束處。包括表達式語句(如賦值a = b;),返回語句,if、switch、while、do-while語句的控制表達式,for語句的3個表達式。
函式調用時的函式入口點。函式實參的求值順序未指定,但順序點意味著這些實參求值的副作用在進入函式時都已經完成。表達式f(i++) + g(j++) + h(k++),調用f(), g(), h()的順序未指定,i, j, k的自增順序也未指定。函式調用f(a,b,c)的實參列表不是逗號運算符,a, b, and c的求值順序未指定。
函式返回時,在返回值已經複製到調用上下文。(僅C++標準指出這一順序點[6])
初始化的結束。例如,聲明int a = 5;中的對5求值之後。
初始化列表的以逗號分割的各個初始化值,嚴格遵照從左至右求值。例如:int a[3] = {i++,j--,foo(101)};注意,此處不是逗號運算符。(從C++11標準指出這一順序點)
在聲明序列的每個聲明(declarator)之間。例如,int x = a++, y = a++的兩次a++求值之間。[7]注意,此例不是逗號運算符。
1.&&(邏輯與)、||(邏輯或)、逗號運算符的左運算元與右運算元求值之間(前兩者是短路求值的一部分)。例如,表達式*p++ != 0 && *q++ != 0,子表達式*p++ != 0的副作用都會在試圖訪問q之前完成。
2.三元條件運算符的第一個運算元之後,第二或第三運算元之前。例如,表達式a = (*p++) ? (*p++) : 0在第一個*p++之後存在順序點,因而在第二個*p++求值之前已經做完一次自增。
3.完整表達式結束處。包括表達式語句(如賦值a = b;),返回語句,if、switch、while、do-while語句的控制表達式,for語句的3個表達式。
4.函式調用時的函式入口點。函式實參的求值順序未指定,但順序點意味著這些實參求值的副作用在進入函式時都已經完成。表達式f(i++) + g(j++) + h(k++),調用f(), g(), h()的順序未指定,i, j, k的自增順序也未指定。函式調用f(a,b,c)的實參列表不是逗號運算符,a, b, and c的求值順序未指定。
5.函式返回時,在返回值已經複製到調用上下文。(僅C++標準指出這一順序點[6])
6.初始化的結束。例如,聲明int a = 5;中的對5求值之後。
7.初始化列表的以逗號分割的各個初始化值,嚴格遵照從左至右求值。例如:int a[3] = {i++,j--,foo(101)};注意,此處不是逗號運算符。(從C++11標準指出這一順序點)
8.在聲明序列的每個聲明(declarator)之間。例如,int x = a++, y = a++的兩次a++求值之間。[7]注意,此例不是逗號運算符。