1. 程式人生 > >《C陷阱與缺陷》讀書筆記

《C陷阱與缺陷》讀書筆記

這本書很薄,僅有150來頁,正文大概120頁,最後附上了課後答案及建議。上週花了三天斷斷續續看完,先做一個總結。

第一章:詞法陷阱

  1. 詞法分析中的貪心演算法:每個符號應該包括儘可能多的字元。因此,註釋的巢狀是不允許的。
  2. 符號的中間不能嵌有空白(空格符、製表符和換行符)。例如,==是單個符號,= =則是兩個符號。例如,
    a---b ;
    a -- - b ;
    a - -- b ;

    前兩項含義相同,而不同於第三項。同樣的,如果/是為判斷下一個符號而讀入的第一個字元,而/之後緊接著*,那麼無論上下文如何,這兩個字元都將被當做/*,表示一段註釋的開始。例如

    y= x/*p;    /* p指向除數 */

    上述語句本意似乎是用x除以p所指向的值,再把所得的商賦給y。而實際上,/*被編譯器理解為一段註釋的開始,編譯器將不斷的讀入字元,直到*/出現為止。上面的語句應當重寫為下面的格式,得到的實際效果才是語句註釋所表達的意思。

    y = x / *p;          /* p指向除數*/
    
    y= x/(*p);           /* p指向除數*/

                                                              

  3. 用雙引號引起的字串,代表的是一個指向無名陣列起始字元的指標,該陣列被雙引號之間的字元以及一個額外的二進位制為零的字元‘0’初始化。也就是說

    printf("hello\n");
    與
    char hello[]={'H','E','L','L','O','\n',0};
    printf(hello);
    是等效的

     

第二章 詞法陷阱

  1. 假定fp是一個指向返回值為void型別的函式的指標,那麼fp的宣告如下:
    void (*fp)()

    呼叫fp所指向函式的方法如下:

    (*fp)();

    在表示式(*fp)()中,*fp兩側的括號非常重要,因為函式運算子()的優先順序高於單目運算子*。

  2. 若知道了如何宣告一個變數,那麼就可以對一個常數進行型別轉換,將其轉換為該變數的型別:只需要在變數宣告中將變數名去掉即可。因此,將常數0轉換為“指向返回值為void型別的函式的指標”型別,可以這樣寫

    (void (*)()) 0

    因此我們可以用(void (*)()) 0來代替fp,從而得到

    *( (void (*)()) 0 ) ();

    末尾的分號使得表示式成為一個語句。

  3. 運算子的優先順序中,最重要的兩點:a,總則:0目(陣列下標,函式呼叫,結構成員選擇)  > 單目 > 雙目(算術,移位,關係依次遞減) > 三目 > 賦值;b,任何一個邏輯運算子的優先順序低於任何一個關係運算符;移位運算子的優先順序比算術運算子低,但是比關係運算符高。

第三章 語義陷阱

  1. 陣列和指標:在講陣列作為函式的引數傳遞的時候,陣列會自動被轉化為指標的形式。
  2. 在C語言中將一個整數轉換為一個指標,最後得到的結果取決於具體的C編譯器的實現。
  3. 宣告陣列:
    int a[3];

    上式聲明瞭a是一個擁有3個整形元素的陣列。類似的,

    struct {
        int p[4];
        double x;
    }b[17]

    上式聲明瞭b是一個擁有17個元素的陣列,其中每個元素都是一個結構,該結構中包括了一個擁有四個整形元素的陣列(命名為p)和一個雙精度型別的變數(命名為x)。現在考慮下面的例子,

    int calendar[12][31]

    這個語句聲明瞭calendar是一個數組,該陣列擁有12個十足型別的元素,其中每個元素都是一個擁有31個整形元素的陣列因此,sizeof(calendar)的值是372(31*12)與sizeof(int)的乘積。

  4. 如果一個指標ip指向的是陣列中的一個元素,那麼ip+1就指向該陣列種下一個元素的地址。對於除1之外的其他整數型別,以此類推。例如:

    *(calendar+4)= calendar[4]

     

  5. 如果兩個指標只想的是同一個陣列中的元素,那麼兩個指標相減是有意義的,而相加沒有意義。對於兩個指向不同陣列的指標,相加和相減都沒有意義。

  6. 引用越界元素非法,但引用越界元素指向的地址合法。(P50)

  7. 整數溢位:首先要明確一點,無符號算數運算中,沒有“溢位”這一說(另外,一個無符號數和一個有符號數進行運算,有符號數會轉換成無符號數)。溢位只針對兩個都是有符號的算術運算。對於兩個均為有符號數的算術運算,若a和b為非負整數,檢查a+b是否會溢位? a+b < 0 是不能正確執行的。如某些機器上,資料運算的結果為正/負/零/溢位四種,則a+b時,內部暫存器會出現溢位而不是負,則if檢查失敗。正確方式是將a和b都轉換成無符號整數。if((unsigned)a + (unsigned) b > INT_MAX),其中INT_MAX在<limits.h>中所定義。或者 if(a > INT_MAX -b)。

第四章 連線

  1. 連結器的作用:輸入一組目標模組和庫檔案,輸出一個載入模組。其主要作用是解決命名衝突。
  2. static修飾符(靜態全域性變數)作用有二:其一是宣告變數或者函式是靜態的;其二是被static修飾的變數或者函式只能在該原始檔內引用。換句話說,如果一個函式僅僅被同一個原始檔的其他函式呼叫,就應該宣告該函式為static。這樣可以避免與標準庫檔案中的外部物件名稱發生衝突。
  3. 靜態全域性變數static和其他全域性變數的儲存地點並沒有區別,都是在data段(已初始化)或.bss段(未初始化)內。但是它只在定義它的原始檔內有效,其他原始檔無法訪問。
  4. 如果一個未標明的識別符號後面跟一個開括號,那麼它將被視為一個返回整型的函式。
  5. 如果一個函式在被定義或者宣告之前被呼叫,那麼它的返回型別就預設為整形。
  6. 每個外部物件只在一個地方宣告。這個宣告的地方一般就在一個頭檔案中,需要用到該外部物件的所有模組都應該包括這個標頭檔案。特別需要指出的是定義該外部物件的模組也應該包括這個標頭檔案。

第五章 庫函式

  1. signal訊號處理函式需要儘可能的簡單。

第六章 前處理器

  1. 不能忽視巨集定義中的空格。
  2. 巨集並不是函式。
  3. 巨集並不是語句。
  4. 巨集並不是型別定義。型別定義請使用typedef。

第七章 可移植性缺陷

  1. 一個常見的錯誤是:如果c是一個字元變數,使用(unsigned)c就可以得到與c等價的無符號整數。這是會失敗的,因為在將字元c轉換為無符號整數時,c將首先被轉換為int型整數,而此時可能得到非預期的結果。正確的做法是:使用語句(unsigned char)c,因為一個unsigned char型別的字元在轉換為無符號數時無需首先轉換為int整形,而是直接進行轉換。
  2. 在移位運算中,如果被移位的物件是n位,那麼移位計數必須大於或等於0,而嚴格小於n。
  3. 移位運算的速度遠大於除法運算。
  4. null指標並不指向任何向量。
  5. 呼叫malloc(n)將返回一個指標,指向一塊新分配的可以容納n個字元的記憶體,程式設計者可以使用這塊記憶體。把malloc函式返回的指標作為引數傳入給free函式,就釋放了這塊兒記憶體,這樣就可以重新利用了。