閱讀《C陷阱與缺陷》的知識增量

看完《C陷阱與缺陷》,忍不住要從新翻一下,記錄一下與本身的慣性思惟不符合的地方。記錄的是知識的增量,是這幾天的流量,而不是存量。
這本書是在ASCI C/C89訂製以前寫的,有些地方有疏漏。編程

第一章 詞法陷阱

  • 1.3 C語言中解析符號時使用貪心策略,如x+++++y將被解析爲x++ ++ +y,並編譯出錯。
  • 1.5 單引號引發的一個字符表明一個對應的整數,對於採用ASCII字符集的編譯器而言,'a'與014一、97含義一致。
  • 練習1.1 嵌套註釋(如/*/**/*/)只在某些C編譯器中容許,如gcc4.8.2編譯時是不支持的。

第二章 語法陷阱

  • 2.6 else始終與同一個括號內最近的未匹配的if結合

第三章 語義陷阱

  • 3.1 int a[12][31]表示的是一個長度12的數組,每一個元素是一個長度31的數組。
  • 3.1 在須要指針的地方若是使用數組名來替換,那麼數組名就被視爲其下標爲0的元素的指針,p = &a的寫法是非法的(gcc4.8.2只是警告)。
  • 3.2 如何鏈接兩個給出的字符串s、t?細節很重要,書中給出的答案以下:
char *r,*malloc()

//原文稱不能直接聲明一個s、t長度之和的數組,但c99能夠聲明變長數組,已經能夠了
//記得要把長度加1
r = malloc(strlen(s) + strlen(t) +1);

//必須判斷內存是否分配成功
if(!r){
    complain();
    exit(1);
}

strcpy(r,s);
strcat(r,t);

......

//完成以後必定要釋放r
free(r);
  • 3.6 如何正確計算數組的邊界?原則一,考慮最簡單狀況下的特例;原則二,仔細計算邊界。
  • 3.6 如下一段代碼爲什麼引發死循環?這是由於在內存地址遞減時,a[10]就是i。
    int i,a[10];
    for(i = 1; i<=10; i++)
    a[i] = 0;
  • 3.6 邊界的編程技巧:用第一個入界點和第一個出界點表示數值範圍,即[low,high)。這樣的效果是
    • 取值範圍的大小爲二者之差。
    • 若取值範圍爲空,則上界等於下界。
  • 3.6 --n通常比n--執行速度更快。
  • 3.7 運算符&&和||保證兩個操做數從左至右求值,其餘運算符的操做數求值順序未定義。好比y[i] = x[i++]結果是未定義的。
  • 3.9 如何檢測a+b是否溢出?
    • if(a+b < 0)是不正確的,由於溢出時的行爲是未定義的。正確的方法是將二者轉換爲unsigned型與INT_MAX比較
    • 更巧妙的方法:if(a > INT_MAX - b)

第四章 鏈接

  • 4.2 int a若出如今全部函數體以外,則完成了聲明與定義(分配存儲空間)。而extern int a;只是聲明,說明a的存儲空間是在其餘地方分配的,不是定義;所以必須在別的某個地方定義,同一個或不一樣的源文件都可。
  • 4.3 static修飾符能夠將一個函數或變量的做用域限制在一個源文件以內,不會與其餘文件中的同名量發生衝突
  • 4.5 聲明與定義必須嚴格相同,而數組和指針是不一樣的。
  • 4.6 如何避免聲明與定義不符?遵照「每一個外部對象只在一個地方聲明」的規則便可。通常放在頭文件中,全部用到此外部對象的源文件都要包括此頭文件,定義此對象的文件也應該包括此頭文件。

第五章 庫函數

  • 5.1 getchar()返回整數,不能把返回值賦值給char型變量再與EOF比較,由於EOF定義爲-1,應該賦值給int型變量。
  • 5.2 若是要對文件進行連續的read和write操做,則中間必須插入fseek函數調用。
  • 5.3 setbuf(stdout, buf);能夠強制將buf指向的char數組設爲緩衝區,改變輸出緩存大小。
  • 5.3 書中使用緩衝區把stdin的內容複製到stdout的程序是錯誤的,由於緩衝區內容的寫出直到緩衝區滿或調用fflush纔開始完成。能夠把buf聲明爲靜態的或者malloc在堆中,防止main函數結束後buf清空。
  • 5..1 一個程序異常終止時,程序輸出的最後一部分經常丟失,可使用setbuf指向一個空指針做爲緩衝區
  • 5..2 putchar/getchar在stdio.h中使用宏實現,若是沒有包括stdio.h,很大可能仍能運行,可是使用相應的函數代替,速度下降。

第六章 預處理器

  • 6 宏只是對文本處理,是一個表達式,不是函數或語句
  • 6.1 宏定義最好把每一個參數和整個表達式使用括號括起來防止出錯。
  • 6.2 若是一個操做數在兩個地方用到,將被求值兩次。解決方案:操做數應該沒有反作用;將宏實現爲函數。
  • 6.2 宏可能產生很是龐大的表達式。
  • 6.3 宏的分號的使用很麻煩,assert的一種正確實現:#define assert(e) ((void)((e)||_assert_error(__FILE__,__LINE__)))
  • 6.4 typedef struct foo FOOTYPE是類型定義語句,定義了一個新的類型。

第七章 可移植性缺陷

  • 7.4 編譯器實現可能將字符看成有符號或無符號的。char轉換爲int時結果未定義,可使用unsigned char避免。
  • 7.4 將字符變量轉換爲無符號整數時應該使用(unsigned char)c而不是(unsigned)c,後者將c轉換爲int再轉換爲unsigned int。
  • 7.5 除法運算速度大大慢於移位。
  • 7.7 整數除法運算時,僅規定商 x 除數 + 餘數 == 被除數,大多數實如今負數的除法時,只保證餘數與被除數正負號相同,商與被除數的符號無關。應儘可能使n爲無符號數。
  • 7.9 toupper/tolower函數均採用int型參數,實現時要檢查輸入是否符合要求,採用置位實現很是快速。
  • 7.11 要求一個按位輸出long型數字。須要考慮:不能對-n求值,可能溢出(邊界條件),應該把n轉換爲負的再處理;餘數的符號未知,應作歸一化處理。
  • 7..2 atoi函數把字符串轉換爲long型整數,應該按照負數來處理以免溢出。
相關文章
相關標籤/搜索