1. 程式人生 > >《C陷阱與缺陷》整理一

《C陷阱與缺陷》整理一

1.詞法分析中的“貪心法”
    C語言的某些符號,例如/、*和=,只有一個字元長,稱為單字元符號。而C語言中的其他符號,例如/*和==,以及識別符號等都包含了多個字元,稱為多字元符號。當C編譯器讀入一個字元'/'後又跟了一個字元'*',那麼編譯器就必須做出判斷:是將其作為兩個分別的符號對待,還是合起來作為一個符號來對待。C語言對這個問題的解決方案可以歸納為一個很簡單的規則:每一個符號應該包含儘可能多的字元。也就是說,編譯器將程式分解成符號的方法是,從左到右一個字元一個字元地讀入,如果該字元可能組成一個符號,那麼再讀入下一個字元,判斷已經讀入的兩個字元組成的字串是否可能是一個符號的組成部分;如果可能,繼續讀入下一個字元,重複上述判斷,直到讀入的字元組成的字串已不再可能組成一個有意義的符號。
    但是需要注意的是,除了字串與字元常量,符號的中間不能嵌有空白(空格符、製表符和換行符)。注意下面兩個例子:
a---b等價於a -- - b
a---b不等價於a - -- b
y = x/*p 不能實現預期的目的,可以修改為y = x/(*p)或y = x/ *p

2.字串常量

用雙引號引起的字串,代表的是一個指向無名陣列起始字元的指標,該陣列被雙引號之間的字元以及一個額外的二進位制值為0的字元'\0'初始化。

3.'yes'的處理
'yes'這種寫法的字元,根據編譯器的不同會得到不同的處理:大多數編譯器會理解為一個整數值,由'y'、'e'、's'所代表的整數值按照特定編譯器實現中定義的方式組合得到;Borland C++v5.5和LCC v3.6採取的做法是忽略多餘的字元,最後的整數值即第一個字元的整數值;在Visual C++6.0和GCC v2.95中採取的做法是,依次用後一個字元覆蓋前一個字元,最後得到的整數值即最後一個字元的整數值。

4.C語言變數的宣告
    任何C變數的宣告都由兩部分組成:型別以及一組類似表示式的宣告符。宣告符從表面上看與表示式有些類似,對它求值應該返回一個宣告中給定的型別的結果。
float ff();
這個宣告的含義:表示式ff()求值的結果是一個浮點數,也就是說,ff是一個返回值為浮點型別的函式。
float *pf;
這個宣告的含義:*pf是一個浮點數,也就是說pf是指向一個浮點數的指標。
float *g();
這個宣告的含義:*g()是一個浮點數,g()等價於上面的pf一樣理解,g是一個函式,函式的值就是函式的返回值,所以g()返回的值應該是上例中的pf一致的型別,也就是一個浮點型的指標。
float (*h)();
這個宣告的含義:(*h)()是一個浮點數,然而(*h)()是一個函式,所以h就是一個函式指標,h指向的這個函式返回一個浮點型的數。

5.強制型別轉換符的構建

從宣告變量出發,去掉宣告中的變數名和宣告末尾的分號,然後將剩餘的部分用一個括號括起來即可。
float (*h)();-->(float (*)())
後面的強制型別轉換符的含義:返回值為浮點型別的函式的指標。
現在來分析(*(void(*)())0)()的含義
很明顯在常數0之前是一個強制型別轉換符:(void(*)()),這個強制型別轉換符的含義是:返回值型別為void型別的函式指標。所以這整個表示式的含義就是,將0轉換為一個返回值型別為void型別的函式指標,然後通過*運算子取得這個0所指向的函式,然後呼叫它。使用typedef來實現這個功能更為清晰:
typedef void (*funcptr)();
(*(funcptr)0)();
複雜一點的一個例子:
void (*signal(int, void(*)(int)))(int);
含義:首先把signal(int, void(*)(int))看做一個整體,這樣的話這個整體應該代表的是一個函式指標,外層通過*運算子呼叫這個指標指向的函式。然而signal(int, void(*)(int))本身就比較好理解了,signal是一個函式,接收的引數第一個為int型別的值,另一個引數為一個函式指標。然而既然這個整體表示一個函式指標,前面也提到過函式本身代表一個值的話其實也就是說該函式的返回值是一個什麼樣的值,所以signal函式返回一個函式指標,該指標型別為void (*)(int)。那麼void (*signal(int, void(*)(int)))(int)宣告的signal如何呼叫呢?其實用法很簡單:
signal(int m, void(*ptr)(int);就可以實現呼叫,上面這個宣告方式的用處在於使得這個函式的返回值變得更加有意義,呼叫者可以定義一個void (*)(int)型別的指標來接收signal函式的返回值。
這個功能同樣可以由typedef來實現,同樣會變得更為清晰:
typedef void (*HANDLER)(int);
HANDLER signal(int, HANDLER);

6.當需要為一個變數新增符號的時候可以採用下面的這種寫法:

int a = 3;
a = -a;

7.C語言的初始化例表中允許多書寫一個逗號,這有什麼作用?
分析:這是為了詞法分析器的方便,這樣書寫之後,每一個用來初始化的變數都是以逗號結束,編譯器處理起來更為方便。

8.C語言對陣列的名的理解
    定義一個數組a之後,a除了作為運算子sizeof的引數之外,其他的所有情形中a都代表的是a中下標為0的元素的指標,這個在多維陣列的情況下仍然適用,因為在C語言中多維陣列是通過一維陣列模擬出來,只不過一維陣列的每個元素都可以是其他的各種型別的變數,當然也可以是另一個數組。所以如下的例子:
int a[3];
*a = 2;
*(a+1) = 3;
分別操作的是a陣列中的第0個元素和第1個元素,但是這樣操作起來往往比較麻煩,所以現在常用的寫法a[0]、a[1]、a[2]是上面的寫法的簡寫形式。*(a+1)等價於*(1+a),而*(a+1)的簡寫形式為a[1],而*(1+a)的簡寫形式為1[a],所以a[1]等價於1[a]寫法,很多編譯器都不會報錯,但是不推薦第二種寫法!!

9.C語言對陣列的名的進一步理解
下面的例子輸出會是多少?
int a[2][3];
printf("%d, %d", sizeof(a+1), sizeof(a[1]));
分析:先不說這個輸出結果為多少,首先來一步步分析需要輸出的內容,sizeof(a+1)編譯器在碰到這句話的時候,會把a當做一個指標來處理,因為陣列+1沒有意義,只有陣列的指標+1才有意義,所以這裡測試出來的仍然只是一個指標的大小,那麼如果是32位的PC的話輸出就會是4。再來分析sizeof(a[1]),這個測的是一維陣列a的第2個單元的大小,然而第二個單元裡面存放的是什麼呢?第二個單元裡面存放的是一個3個單元的陣列,所以這個表示式測試出來的值為12。
答案:4, 12

10.malloc函式使用注意

    malloc()函式分配記憶體失敗的時候會返回一個空指標,在使用malloc的時候一定要判斷返回的值是否為空指標,否則會產生意想不到的後果。