1. 程式人生 > >關於自增、自減、順序點、副作用、完整表示式的一些小彙總。

關於自增、自減、順序點、副作用、完整表示式的一些小彙總。

為什麼突然又想起來了寫這麼一篇文章?是因為在看THQ的C程式程式設計的課本時,在看for迴圈看到一個for(i=1;i<=100;i++,i++)等於for(i=1;i<=100;i=i+2),當時不知為何無法理解。再來看while(i++<100) printf("i=%d",i),i是如何的?先用i值和100比較還是自增完畢後和100比較?

首先副作用是什麼?

C primer plus中對副作用(side effect)的定義為:對資料物件或檔案的修改。例如i=5;計算這個表示式的副作用就是把i的值變為5;同樣我們要討論的++和--也有副作用。

第二什麼是順序點。

C primer plus中對順序點的定義為:在該點,所有的副作用都在進入下一步前被計算。例如在C語言中分號就是一個順序點,它意味著像自增自減賦值等運算子所做的全部改變必須在程式進入下一個語句前發生。

第三完整表示式的概念:如果一個表示式不是一個更大表達式的的子表示式,那麼這個表示式就是一個完全表示式。

那麼哪些是順序點呢?(結合維基百科和網上網友的回答)

1)分號;

   2)未過載的逗號運算子的左運算元賦值之後(即','處)

   3)未過載的'||'運算子的左運算元賦值之後(即'||'處);

   4)未過載的'&&'運算子的左運算元賦值之後(即"&&"處);

   5)三元運算子'? : '的左運算元賦值之後(即'?'處);

例如,例1:表示式*p++ != 0 && *q++ != 0,子表示式*p++ != 0的副作用都會在試圖訪問
q之前完成。

    例2:表示式a = (*p++) ? (*p++) : 0在第一個*p++之後存在順序點,因而在第二個*p++求值之前已經做完一次自增。(對於這個我想說一下,第一個*p++和第二個*p++都是使用的表示式的值也就是*p的值,這個可以通過對程式的彙編看出來,只不過第一個的值是1,第二個的值是2)

 6)在函式所有引數賦值之後但在函式第一條語句執行之前;函式實參的求值順序未指定,但順序點意味著這些實參求值的副作用在進入函式時都已經完成。就是這個引起了下列的問題。

 7)在函式返回值已拷貝給呼叫者之後但在該函式之外的程式碼執行之前;(根據維基所說,現在只有C++指出了這個順序點)

 8)每個基類和成員初始化之後;

   9)在每一個完整的變數宣告處有一個順序點 例如,int x = a++, y = a++的兩次a++求值之間

10)完整表示式結束處。包括表示式語句(如賦值a=b;),返回語句ifswitchwhiledo-while語句的控制表示式,for語句的3個表示式。while(i++<100) printf("i=%d",i)這個語句,C保證副作用(增加i的值)在程式進入printf()前發生。同時使用字尾形式i在和100比較後才增加。根據《C語言程式設計-現代方法》所講,for語句的第一個和第三個表示式都是以語句的方式執行的,所以剛好可以利用它們的副作用;其結果就是這兩個表示式常常作為賦值表示式或者自增/自減表示式。

--------------------------------------------------------------------------------------------------------------------------------------------在C語言吧提的一個問題,感謝謝應宸吧友的解答---------------------------------------------------------------------------------------------------------------------------------------

C_Primer_Plus(第五版P218)中說當一個函式被呼叫時,將建立被宣告為形式引數的變數,然後用計算後得到的實際引數的值初始化該變數。


C語言程式設計—現代方法(P43)函式呼叫執行之前實際引數必須全部計算出來。如果實際引數恰巧是含有++或者--運算子的表示式,那麼必須在呼叫發生前進行自增貨自減操作


在對順序點的學習中知道函式呼叫時的函式入口點是順序點。既然是順序點的話那麼在進入函式之前,函式的實參求值的副作用在進入函式時都已經完成。


那麼問題來了:


#include <stdio.h>
#include <stdlib.h>
int main()
{
int a,b;
a=1;
b=test(a++);
printf("a=%d,b=%d",a,b);
system("pause");
return 0;
}
test(x)
{
int z;
z=x;
return z;
}


按照順序點以及兩本書所說的,a++在呼叫之前已經完成自增,即傳給形參的值應該是2.但是實際上結果是 2,1.請問這是為什麼?

解答:假設有


雖然你看到的呼叫程式碼就一行
其實是有好幾條彙編指令的
詳細的可見我以前寫的關於函式呼叫的帖子(http://tieba.baidu.com/p/3422836606?pid=60597208037&cid=0#60597208037


簡單的講就是說最終我們呼叫函式其實就是一條簡單的call指令,之前會做引數的傳遞等準備工作,有的用壓棧方式,有的用暫存器傳參等等,根據書中的說法就是,實參的操作需在call指令執行之前完成,但是這個跟傳參是無關的


可以看下在vc和cfree下兩者的反編譯結果



是不是都如書中說的add指令和incl指令都在call之前完成了呢。


根據反編譯就可以看出其順序是
壓棧傳參
自增
呼叫

在呼叫函式時所有準備工作做完到call之前是一個順序點,在此之前自增或自減的操作會完成。比如說a是一個全域性變數,當呼叫foo(a++)時,會產生好幾條彙編指令,其中最後一條是call,那麼在這個順序點前所有的副作用必須完成,也就是說a++已經完成了。那麼在進入foo函式後a的值已經自增1了,而不是在函式呼叫結束之後再自增的。