2013年11月21於浙大華家池算法
C 語言反作用:安全
(side effect)是指對數據對象或者文件的修改。ide
例如,語句 v = 99;的反作用是把 v 的值修改爲 99。函數
C語言序列點:優化
(sequence point)是指程序運行中的一個特殊的時間點,在該點以前的全部反作用已經結束,而且後續的反作用還沒發生,而兩個序列點之間全部的表達式或代碼執行順序是未定義的。spa
1). 一個重要的序列點在完整表達式的結尾(即分號),所謂完整表達式,就是說這個表達式不是子表達式。而所謂的子表達式,則是指表達式中的表達式。也就是說,C 語句中由賦值、自增或者自減等引發的反作用在分號(序列點)以前必須結束。code
例如:
a= ++b % 3;對象
這整個表達式就是一個完整表達式。這個表達式中的 ++b、3 和 ++b % 3 都是它的子表達式。blog
有了序列點的概念,咱們下面來分析一下一個很常見的錯誤:
int x = 1, y;
y = x++ + x++;
這裏 y = x++ + x++ 是完整表達式,而 x++ 是它的子表達式。這個完整表達式運算結束的那一點是一個序列點,int x = 1, y; 中的 ; 也是一個序列點。也就是說,x++ + x++ 位於兩個序列點之間。C標準規定,在兩個序列點之間,一個對象所保存的值最多隻能被修改一次。可是咱們清楚能夠看到,上面這個例子中,x 的值在兩個序列點之間被修改了兩次。這顯然是錯誤的!這段代碼在不一樣的編譯器上編譯可能會致使 y 的值有所不一樣。比較常見的結果是 y 的值最後被修改成 2 或者 3。編譯器
2). 逗號表達式。逗號表達式會嚴格的按照順序來執行而且每個逗號分隔都是一個序列點,因此,前一個逗號表達式若是是i++,則後面的表達式能夠確定如今的值是原來的值加1(若是有溢出則另當別論)。
如:
int i = 1;
i++, i++, i++;
printf("%d\n", i);
如今的i確定是4;
那麼如下結果呢(注能夠在程序編譯的時候在上-Wsequence-point選項):
int a = 1, b = 3;
a = 10, a++, b++;
printf(「a = %d, %d\n」, a, b);
3). &&和||運算符。有一種短路算法,即&& 和 ||序列點必須先肯定左邊表達式的值。
以下:
int a = 10;
int b = 0;
if (b && a/b)
{ /* some code here */ }
其中在求b的值的時候會短路,即,a/b不會執行。由於b的值爲0,可是在此處咱們能夠放心的使用除法。&&,||這兩個運算符在使用的時候均可以當成一個序列點,若是前一個表達式的值已經能夠認定這整個表達式的值爲真或者爲假,則後面的表達式沒有必要再求值,是多餘的。即如上面的a/b是多餘的,不能求值,求值也會出錯。它們之間的求值順序是確定的。
再好比:
int a,b,c;
a=b=c=1;
++a || ++b && ++c;問執行後a、b、c的值各是多少?
在此處雖然&&的優先級比||高,可是||, &&都是序列點,因此只有||序列點以前的執行完後纔會執行&&操做。(注意C語言沒有規定計算順序,固然也不是誰的優先級高就先執行誰, 先肯定優先級高的結合性)。
4). 條件運算符「? :」 在問號(?)的地方也存在一個序列點,即問號先後能夠訪問和改變同一個變量,而且這種訪問是安全的。
例如:
int a = 1, b = 4;
a > b ? ++a : ++b;
若是條件操做符沒有序列點,語句a > b ? ++a : ++b;執行時,++a和++b會先於子表達式a > b執行; 可是,條件操做符?:的問號處有序列點,因此語句」 a > b ? ++a : ++b; 「執行時,問號處?左邊的操做數a > b先執行,值爲真時,對++a求值,不對++b求值;值爲假時,正好相反。
那麼請分析:
int a = 0, b= 2;
int m = a++ ? b : a ;
再分析:
int a = 1, b = 2;
a > b ? a : a+2;
5). 函數調用時,實參表內所有參數求值結束,函數的第一條指令執行以前(注意參數分隔符「,」不是序列點);
int a = 3;
printf(「%d, %d\n」, a, a++);
以上結果是未定義的
最後,在一個表達式內的求值順序沒有固定順序,還有一個表現是,以下:
funa() + funb() + func();
C語言標準沒有規定這三個函數誰會先執行,若是對順序有要求,能夠用臨時變量來緩解。
寫到這裏想起一道題,原來之前個人理解和解釋都是錯的, 關鍵問題是此題三個fun函數計算順序是未定義的
1 int fun() 2 { 3 static int a = 1; 4 return a++; 5 } 6 int main() 7 { 8 int sum = fun() - fun()*fun(); 9 printf("sum = %d\n", sum); 10 11 return 0; 12 }
注意:
1)C 語句中由賦值、自增或者自減等引發的反作用在序列點以前必須結束
2)優先級隻影響結合性,與計算順序無關
3)複雜表達式分析:一分析結合性,二分析序列點
4)c語言爲了高效性沒有規定計算順序,這樣給了編譯器更大的優化空間
警告:
定義序列點是爲了儘可能消除編譯器解釋表達式時的歧義,未定義狀況後果程序猿自負。