C語言序列點問題總結(大多數高等教育C語言教學課程的漏洞)


            C語言序列點總結

                                                                             2013年11月21於浙大華家池

 

C 語言副作用:

(side effect)是指對數據對象或者文件的修改。

例如,語句 v = 99;的副作用是把 v 的值修改成 99。

C語言序列點:

(sequence point)是指程序運行中的一個特殊的時間點,在該點之前的所有副作用已經結束,並且后續的副作用還沒發生,而兩個序列點之間所有的表達式或代碼執行順序是未定義的。

1). 一個重要的序列點在完整表達式的結尾(即分號),所謂完整表達式,就是說這個表達式不是子表達式。而所謂的子表達式,則是指表達式中的表達式。也就是說,C 語句中由賦值、自增或者自減等引起的副作用在分號(序列點)之前必須結束。

例如:
a= ++b % 3;

這整個表達式就是一個完整表達式。這個表達式中的 ++b、3 和 ++b % 3 都是它的子表達式。

 

有了序列點的概念,我們下面來分析一下一個很常見的錯誤:
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語言為了高效性沒有規定計算順序,這樣給了編譯器更大的優化空間

 

警告:

定義序列點是為了盡量消除編譯器解釋表達式時的歧義,未定義情況后果程序猿自負。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM