讀《C陷阱與缺陷》


《C陷阱與缺陷》里面介紹了一些自己不知道和以前理解不深的東東,現總結如下:

1.詞法分析的陷阱(本書第9頁)

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

上述語句的本意是:用x除以指針p所指向的值,然后把商賦給y;但是/*被編譯器理解為一段注釋的開始,編譯器將不斷地讀入字符,直到*/出現為止。也就是說該語句實際的執行效果只是將x的值賦給y而已;

可以將上面的語句重寫成如下格式:

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

【備注】:我們的項目組中,明確規定在運算符與變量之間必須添加空格,就是為了避免上面的錯誤;

 

2.運算符優先級(本書第22頁)

關於運算符優先級,我們需要記住兩點:
I.任何一個邏輯運算符的優先級低於任何一個關系運算符;
II.移位運算符的優先級比算術運算符要低,但是比關系運算符要高;

 

3.數組的邊界計算與不對稱邊界(本書第45頁)

int i, a[10];

for (i=1; i<=10; i++)
    a[i] = 0;

上述代碼的錯誤非常明顯,數組a中並不存在a[10]這個元素,而在for循環中卻引用了這個變量。如果用來編譯這段程序的編譯器按照內存地址遞減的方式來給變量分配內存,那么內存中數組a之后的一個字(word)實際上是分配給了整型變量i。此時,本來循環計數器i的值是10,循環體內將並不存在的a[10]設置為0,實際上卻是將計數器i的值設置為0,這就是陷入了一個死循環(本人使用gcc測試了一下,運行該程序確實為以死循環)。這是C語言將數組的下表定義為從0開始的引起的一個弊端。

但是C語言數組的這種不對稱邊界會對程序設計的簡化帶來許多方便之處:
I.數組的長度就是上屆與下屆之差。
II.如果數組的取值范圍為空,那么上界等於下界;
III.即使數組的取值范圍為空,上界也永遠不可能小於下界;

這種不對稱邊界的思考方式使用,是把上界視作某序列中第一個被占用的元素,而把下界視作序列中第一個被釋放的元素。當處理各種不同的類型的緩沖區時,這種看待問題的方式就特別有用。例如,考慮這樣一個函數,該函數的功能是將長度無規律的輸入數據送到緩沖區(即一塊能夠容納N個字符的內存)中去,每當這塊內存被“填滿”時,就將緩沖區的內容寫出。因此,該函數就可以這樣實現:

#define N 1024
static char buffer[N];           
static char *bufptr;             /* 指向緩沖區的當前位置 */

void bufwrite (char *p, int n)
{
        bufptr = &buffer[0];

       while (--n >= 0)
       {
            if (bufptr == &buffer[N])
                flushbuffer();    /* 清空緩沖區 */
            
            *bufptr++ = *p++;
        }
}

雖然不存在buffer[N]這個元素,但是我們並不需要引用這個元素,而只需要引用這個元素的地址,並且這個地址在我們遇到的所有C語言實現中又是“千真萬確”存在的。而且ANSI C標准明確允許這種做法:數組中實際不存在的“溢界”元素的地址衛浴數組所占內存之后,這個地址可以用於進行賦值和比較。當然,如果要引用該元素,那就是非法的了。

 

4.數組和指針的區別(本書第36頁)

int a[10];
int *p = a;

 

上述兩個聲明,sizeof(a) =10*sizeof(int),而sizeof(p)=4。

 

5.關於scanf的參數越界(本書第76頁)

#include <stdio.h>

int main ()
{
    int i;
    char c;
    for (i=0; i<5; i++)
    {
        scranf("%d", &c);
        printf("%d ", i);
    }

    printf("\n");

    return 0;          
}

 

 上段代碼問題關鍵在於,這里c被聲明為char類型,而不是int類型。當scanf讀入一個整數,應該傳遞給它一個指向整數的指針。而程序中scanf函數得到的卻是一個指向字符的指針,scanf函數並不能分辨這種情況,它只是將這個指向字符的指針作為指向整數的指針而接受,並且在指針指向的位置存儲一個整數。因為整數所占的存儲空間要大於字符所占的存儲空間,所以c附近的內存將被覆蓋。c附近的內存中存儲的內容是由編譯器決定的,本例中它存放的是整數i的低端部分。因此,每次讀入一個數值到c時,都會將i的低端部分覆蓋為0,而i的高端部分本來就是0,相當於i每次被重新設置為0,循環將一直進行下去。

 

5.文件的讀寫順序(本書第85頁)

如果要同時進行輸入和輸出操作,必須在其中插入fseek函數的調用。如fwrite之后如想立即fread,則必須在fread之前fseek一下,及時fseek只是將指針偏移了0個字節。如下所示的代碼:

while (fread((char *)&rec, sizeof(rec), 1, fp) == 1)
{
    /* 對rec執行某些操作 */

    if ( /* rec必須被重新寫入 */ )
    {
        fseek(fp, -(long)sizeof(rec), 1);
        fwrite( (char *)&rec, sizeof(rec), 1, fp);
        fseek(fp, 0L, 1);
    }
}

/* 第二個fseek函數看上去什么也沒做,但它改變了文件的狀態,使得文件現在可以正常地進行讀取了 */

 

 

6.盡量不要使用宏來定義新的數據類型(本書第101頁)

#define T1 struct foo *
typedef struct foo *T2;

T1 a, b;
T2 a,b;

 

考慮以上代碼,第一個聲明被擴展為:struct foo *a, b; 在這個語句中a被定義為一個指向結構的指針,而b卻被定義為一個結構(而不是指針)。而第二個聲明,則將a和b都定義為了指向結構的指針,因為第二種T2的行為完全與一個真實的類型相同。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM