這篇文章匯集c語言中與順序有關的內容,包括儲存順序、運算順序等,持續更新,由於小弟能力有限,懇請所有看到這篇拙文的廣大大佬們,積極指正,在此謝過了。
1、運算子的優先順序 - 運算子與運算子之間的優先順序
說起運算順序,就不得不談運算子的優先順序,這裡,祭上《c陷阱與缺陷》一書中給出的c語言運算子優先順序表
記憶原則為:把該優先順序表中的運算子分為三類:
第一等:特權派,嚴格來說它們根本不是運算子,但優先順序是最高的,當然地位是同等的,並且都是自左向右結合,包括:陣列下表、函式呼叫操作符——即開括號()、結構成員選擇操作符;
第二類:單目運算子:這裡特別要注意的是,它們是自右至左結合的!!!因此,*p++會被編譯器解釋成*(p++),即取指標p所指向的物件,然後將p遞增1;
第三類:多目運算子:這裡還有小類:算術運算子》(移位運算子)>關係運算子(帶大小於號的要優於 == 和 !=)>邏輯運算子(包括按位運算子和邏輯運算子)>(條件運算子》賦值運算子》逗號運算子)
注意:int i,j,k;
i=j=k=0; <<=>>k=0;j=k;i=j;
2、求值順序 - 給定乙個運算子,運算元與運算元之間的取值順序
c語言中,只有四個運算子存在著規定的求值順序,分別為:&&、||、?:、,。
c語言中其他所有運算子對其運算元求值的順序都是未定義的,其
運算子取
值順序依具體編譯器而定.特別的,賦值運算子並不保證任何求職順序。
例如:要對a
需要注意的是:對於&&、||、?:、,這四種運算子:
a)運算子&&和運算子||先對左側運算元求值,只在需要時才對右側運算元求值。
例如:
int a=0,b=1;
if(b++||(a=b))
輸出是:a=0,b=2。
b)條件運算子?:有三個運算元a?b:c,先求運算元a的值,根據a的值再去求b或c的值。
c)逗號運算子,這裡
需要注意的是:分隔函式引數的逗號並非逗號運算子。例如x和y在函式f(x,y)中的求值順序是未定義的,而在函式g((x,y))中卻確定了先求x後求y的值得順序。在後乙個例子中,函式g只有乙個引數,這個引數的值是這樣求得的:先對x求值,然後x的值被「丟棄」,接著求y的值。-----《c陷阱與缺陷》
另外,對於函式引數的求值順序和壓棧順序,二者不是一回事。壓棧順序是從右到左,而求值順序則是undefined的,由編譯器實現決定。下面以win7 gcc編譯器為例:
void f(int i,int j,int k)
void main()
輸出為:
這裡,呼叫函式f時,壓棧順序依次為:k->j->i->l->g(即自右至左壓棧);而計算順序為:k->j->i(也為自右至左)。
需要注意是,程式的堆疊是由處理器直接支援的。在intel x86的系統中,堆疊在記憶體中是從高位址向低位址擴充套件(這和自定義的堆疊從低位址向高位址擴充套件不同),如下圖所示:
因此,棧頂位址是不斷減小的,越後入棧的資料,所處的位址也就越低。
3、儲存順序 - 大小端
對於跨越多位元組的程式物件,我們在儲存時必須建立兩個規則:這個物件的位址是什麼,以及在儲存器中如何排列這些位元組——《深入理解計算機系統》。
a)第乙個規則:幾乎所有的機器上,多位元組物件都被儲存為連續的位元組序列,物件的位址為所使用位元組中最小的位址。
例如,假設乙個型別為int型的變數x,位址為0x100。也就是說,表示式&x的值為0x100。那麼,x的四個位元組將被儲存在儲存器的0x100,0x101,0x102和0x103的位置。
b)第二個規則:排列表示乙個物件的位元組存在著兩種通用的規制。考慮乙個w位(即用位表示時,有效位數為w)的整數,位表示為[xw-1,xw-2,...,x1,x0]。假設w為8的倍數,則這些位就能被分組為位元組,其中最高有效位元組包含位[xw-1,xw-8],最低有效位元組包含位[x7,x6,...,x0],其他位元組包含中間的位。
某些機器選擇在儲存器中按照從最低有效位元組到最高有效位元組的順序儲存物件,而另一些機器選擇從最高有效位元組到最低有效位元組的順序儲存。前一種規則——最低有效位元組排在前面的方式,稱為小端法(little endian),即低位優先;後一種規則——最高有效位元組在前面的方式,稱為大端法(big endian),即高位優先;(endian)
注意,不管是大端法還是小端法,乙個物件的位元組儲存時,記憶體都是從低位位址向高位位址按順序存放的。即:
大端法——最低位址存放高位位元組,可稱為高位優先,記憶體從最低位址開始,按順序存放;
小端法——最低位址存放低位位元組,可稱為低位優先,記憶體從最低位位址開始,順序儲存。
這裡說的最低位,指的是程式物件的位址,即第乙個規則舉例中,變數x的位址0x100。
舉例說明:假設乙個程式在乙個原始檔(file1.c)中包含宣告:——來自《c陷阱與曲缺陷》,p81
long foo;
而在另乙個原始檔(file2.c)中包含了:
extern short foo;
又進一步假設,如果給long型別的foo賦乙個較小的值,例如37,那麼short型別的foo就同時獲得了乙個值37.我們能夠對執行該程式的硬體做出什麼樣的推斷?如果short型別的foo得到的值不是37而是0,我們又能夠做出什麼樣的的推斷?
《c陷阱與缺陷》給出了如下分析:如果把37賦值給了long型的foo,而short型的foo也得到值37,說明short型的foo與long型的foo包含了值37的有效位的部分,兩者在記憶體中占用的是同一塊區域。可能性有二:(1)long型和short型被實現為同一型別,但很少有c語言實現會這樣;(2)更有可能的是,long型的foo低位部分與short型的foo共享了相同的記憶體空間,一般情況下,這個部分所處的記憶體位址較低。因此我們的乙個可能推論就是,執行該程式的硬體是乙個低位優先(little-endian)的機器。同樣的道理,若short型的foo的值是0是,我們所用的硬體可能是乙個高位優先(big-endian)的機器。
我覺得沒有這麼麻煩,file1.c中定義了long型的foo,那麼在記憶體中就分配了一塊sizeof(long)位元組的記憶體,(32位作業系統下,long型佔4位元組,short型佔2位元組)(short型佔位元組數不得大於int型佔位元組數,int型佔位元組數不大於long型佔位元組數),並且取名foo,注意,這塊記憶體與名字foo生死相依啊,同生共死的。file2.c中宣告short型foo,注意,這裡宣告只是告訴編譯器,foo已經在其他地方定義過了,file2.c引用foo時,首先找到long型foo的位址&foo,然後一起取sizeof(short)個位元組作為short型foo對應的記憶體。如果所使用的機器是little-endian的,那麼short型foo的值就是37,如果所使用的機器是big-endian的,那麼short型的foo的值就是0。
4、for迴圈
首先,考慮下面這段**輸出是什麼。
#include int main()
return 0;
}
這段**是檢測for迴圈運算邏輯的乙個經典例子,輸出為:
是了,for迴圈結構表示為:
for(表示式1;表示式2;表示式3)
那麼,執行順序為:period1:表示式1->表示式2->do someshing->表示式3
period2:->表示式2(若表示式2為真,往下進行,否則,至此終止)->do something->表示式3
......... ........
注意:1、上面的例子中,若表示式2寫成:
i<10,printf("second,i=%d\n",i)
那麼,該迴圈就不會跳出,知道為什麼嗎?哈哈,逗號表示式!!!是的,i<10和printf("second,i=%d\n",i),加上中間的逗號,構成了逗號表示式,函式printf(「second,i+%d\n」,i)的返回值作為表示式2的值,(我在gcc編譯器下printf返回值為3),也就是說,表示式2的值恒為真,無限迴圈。 2、
int i=0;
while(i++ <10)
這裡要十分注意,條件判斷中i的取值為0~9,但執行語句中,i的取值為1~10!i與10比較後,先自加1,然後再執行語句! Python語言中與else有關的
if else語句 和各種語言相同的用法,在條件語句中,與if語句搭配使用的else語句。如果if語句的條件表示式的結果布林值為假,那麼程式將執行else語句後的 它的語法是大家最為熟知的 python if expression expr true suite else expr false su...
C語言中巨集的有關說明
c語言巨集定義簡單函式的好處?因為呼叫函式要有引數壓棧 過程記錄儲存 返回 過程記錄的返回等額外開銷 所以巨集的效率一般要高些。巨集定義不佔執行時間,只佔編譯時間。而函式呼叫則佔執行時間。巨集就是在生成exe等可執行檔案的時候就處理好了,函式則是在執行這個exe檔案的時候才處理。巨集好比是去館子吃飯...
C語言中的 與
連線符號由兩個井號組成,其功能是在帶引數的巨集定義中將兩個子串 token 聯接起來,從而形成乙個新的子串。但它不可以是第乙個或者最後乙個子串。所 謂的子串 token 就是指編譯器能夠識別的最小語法單元。具體的定義在編譯原理裡有詳盡的解釋,但不知道也無所謂。同時值得注意的是 符是把傳遞過來 的引數...