顯示廣告
隱藏 ✕
看板 uefacool
作者 uefangsmith (唉呦!不錯哦~)
標題 Re: [轉寄][重要] 發文前務必閱讀:常見問題十三誡
時間 2011年06月26日 Sun. PM 03:14:56


※ 引述《nowar100.》之銘言:
※ 本文為 uefangsmith 轉寄自 uefang.bbs@ptt.cc 時間: 2011-06-26 15:12:21


  C 語言新手十誡(The Ten Commandments for Newbie C Programmers)

                                                       by Khoguan Phuann
請注意:

(1) 本篇旨在提醒新手,避免初學常犯的錯誤(其實老手也常犯:-Q)。
    但不能取代完整的學習,請自己好好研讀一兩本 C 語言的好書,
    並多多實作練習。

(2) 強烈建議新手先看過此文再發問,你的問題極可能此文已經提出並
    解答了。

(3) 以下所舉的錯誤例子如果在你的電腦上印出和正確例子相同的結果,
    那只是不足為恃的一時僥倖。

(4) 不守十誡者,輕則執行結果的輸出數據錯誤,或是程式當掉,重則
    引爆核彈、毀滅地球(如果你的 C 程式是用來控制核彈發射器的話)。

1. 不可以使用尚未給予適當初值的變數
2. 不能存取超過陣列既定範圍的空間
3. 不可以提取不知指向何方的指標
4. 不要試圖用 char* 去更改一個"字串常數"
5. 不能在函式中回傳一個指向區域性自動變數的指標
6. 不可以只做 malloc(), 而不做相應的 free()
7. 在數值運算、賦值或比較中不可以隨意混用不同型別的數值
8. 在一個運算式中,不能對一個基本型態的變數修改其值超過一次以上
9. 在 Macro 定義中, 務必為它的參數個別加上括號
A(10). 不可以在 stack 設置過大的變數
B(11). 使用浮點數精確度造成的誤差問題
C(12). 不要猜想二維陣列可以用 pointer to pointer 來傳遞
D(13). 函式內 new 出來的空間記得要讓主程式的指標接住
14 . 無法從函式傳回陣列



01. 你不可以使用尚未給予適當初值的變數

    錯誤例子:
    int accumulate(int max)    /* 從 1 累加到 max,傳回結果 */
    {
        int sum;    /* 未給予初值的區域變數,其內容值是垃圾 */
        int num;
        for (num = 1; num <= max; num++) {  sum += num;  }
        return sum;
    }

    正確例子:
    int accumulate(int max)
    {
        int sum = 0;    /* 正確的賦予適當的初值 */
        int num;
        for (num = 1; num <= max; num++) {  sum += num;  }
        return sum;
    }


02. 你不可以存取超過陣列既定範圍的空間

    錯誤例子:
    int str[5];
    int i;
    for (i = 0 ; i <= 5 ; i++) str[i] = i;

    正確例子:
    int str[5];
    int i;
    for (i = 0; i < 5; i++) str[i] = i;


    說明:宣告陣列時,所給的陣列元素個數值如果是 N, 那麼我們在後面
    透過 [索引值] 存取其元素時,所能使用的索引值範圍是從 0 到 N-1

    C/C++ 為了執行效率,並不會自動檢查陣列索引值是否超過陣列邊界,
    我們要自己來確保不會越界。一旦越界,操作的不再是合法的空間,
    將導致無法預期的後果。


03. 你不可以提取(dereference)不知指向何方的指標(包含 null 指標)。

    錯誤例子:
    char *pc1;      /* 未給予初值,不知指向何方 */
    char *pc2 = 0;  /* pc2 起始化為 null pointer */
    *pc1 = 'a';     /* 將 'a' 寫到不知何方,錯誤 */
    *pc2 = 'b';     /* 將 'b' 寫到「位址0」,錯誤 */

    正確例子:
    char c;          /* c 的內容尚未起始化 */
    char *pc1 = &c;  /* pc1 指向字元變數 c */
    *pc1 = 'a';      /* c 的內容變為 'a' */

    /* 動態分配 10 個 char(其值未定),並將第一個char的位址賦值給 pc2 */
    char *pc2 = (char *) malloc(10);
    pc2[0] = 'b';    /* 動態配置來的第 0 個字元,內容變為 'b'
    free(pc2);

    說明:指標變數必需先指向某個可以合法操作的空間,才能進行操作。


    ( 使用者記得要檢查 malloc 回傳是否為 NULL,
      礙於篇幅本文假定使用上皆合法,也有正確歸還記憶體 )

    錯誤例子:
    char *name;   /* name 尚未指向有效的空間 */
    printf("Your name, please: ");
    gets(name);   /* 您確定要寫入的那塊空間合法嗎??? */
    printf("Hello, %s\n", name);

    正確例子:
    /* 如果編譯期就能決定字串的最大空間,那就不要宣告成 char* 改用 char[] */

    char name[21];   /* 可讀入字串最長 20 個字元,保留一格空間放 '\0' */
    printf("Your name, please: ");
    gets(name);
    printf("Hello, %s\n", name);

   正確例子(2):

    /* 若是在執行時期才能決定字串的最大空間,則需利用 malloc() 函式來動態分配空間 */

    size_t length;
    char *name;
    printf("請輸入字串的最大長度(含null字元): ");
    scanf("%u", &length);

    name = (char *)malloc(length);
    printf("Your name, please: ");
    scanf("%s", name);
    printf("Hello, %s\n", name);

    /* 最後記得 free() 掉 malloc() 所分配的空間 */
    free(name);




04. 你不可以試圖用 char* 去更改一個"字串常數"

    錯誤例子:
    char* pc = "john";   /* pc 現在指著一個字串常數 */
    *pc = 'J';   /* 但是 pc 沒有權利去更改這個常數! */



    正確例子:
    char pc[] = "john";  /* pc 現在是個合法的陣列,裡面住著字串 john */
                         /* 也就是 pc[0]='j', pc[1]='o', pc[2]='h',
                                              pc[3]='n', pc[4]='\0'  */
    *pc = 'J';
    pc[2] = 'H';

    說明:字串常數的內容是"唯讀"的。您有使用權,但是沒有更改的權利。
    若您希望使用可以更改的字串,那您應該將其放在合法空間




    錯誤例子:
    char *s1 = "Hello, ";
    char *s2 = "world!";
    /* strcat() 不會另行配置空間,只會將資料附加到 s1 所指唯讀字串的後面,
       造成寫入到程式無權碰觸的記憶體空間 */
    strcat(s1, s2);



    正確例子(2):
    /* s1 宣告成陣列,並保留足夠空間存放後續要附加的內容 */
    char s1[20] = "Hello, ";
    char *s2 = "world!";
    /* 因為 strcat() 的返回值等於第一個參數值,所以 s3 就不需要了 */
    strcat(s1, s2);






05. 你不可以在函式中回傳一個指向區域性自動變數的指標。否則,會得到垃圾值
                                              [感謝 gocpp 網友提供程式例子]
    錯誤例子:
    char *getstr(char *name)
    {
        char buf[30] = "hello, "; /*將字串常數"hello, "的內容複製到buf陣列*/
        strcat(buf, name);
        return buf;
    }

    說明:區域性自動變數,將會在離開該區域時(本例中就是從getstr函式返回時)
    被消滅,因此呼叫端得到的指標所指的字串內容就失效了。

    正確例子:
    void getstr(char buf[], int buflen, char const *name)
    {
        char const s[] = "hello, ";
        strcpy(buf, s);
        strcat(buf, name);
    }

    正確例子:
    int* foo()
    {
        int* pInteger = (int*) malloc( 10*sizeof(int) );
        return pInteger;
    }

    int main()
    {
        int* pFromfoo = foo();
    }

    說明:上例雖然回傳了函式中的指標,但由於指標內容所指的位址並非區域變數,
          而是用動態的方式抓取而得,換句話說這塊空間是長在 heap 而非 stack,
          又因 heap 空間並不會自動回收,因此這塊空間在離開函式後,依然有效
          (但是這個例子可能會因為 programmer 的疏忽,忘記 free 而造成
          memory leak)




    [針對字串操作,C++提供了更方便安全更直觀的 string class, 能用就盡量用]


    正確例子:

    #include <string>    /* 並非 #include <cstring> */
    using std::string;

    string getstr(string const &name)
    {
        return string("hello, ") += name;
    }

06. 你不可以只做 malloc(), 而不做相應的 free(). 否則會造成記憶體漏失

    但若不是用 malloc() 所得到的記憶體,則不可以 free()。已經 free()了
    所指記憶體的指標,在它指向另一塊有效的動態分配得來的空間之前,不可
    以再被 free(),也不可以提取(dereference)這個指標。

    [C++] 你不可以只做 new, 而不做相應的 delete
    注:new 與 delete 對應,new[] 與 delete[] 對應,不可混用
        切記,做了幾次 new,就必須做幾次 delete


   小技巧: 可在 delete 之後將指標指到 0,由於 delete 本身會先做檢查,
           因此可以避免掉多次 delete 的錯誤

   正確例子:
   int *ptr = new int(99);
   delete ptr;
   ptr = NULL;
   delete ptr;   /* delete 只會處理指向非 NULL 的指標 */


07. 你不可以在數值運算、賦值或比較中隨意混用不同型別的數值,而不謹慎考
    慮數值型別轉換可能帶來的「意外驚喜」(錯愕)。必須隨時注意數值運算
    的結果,其範圍是否會超出變數的型別

    錯誤例子:
    unsigned int sum = 2000000000 + 2000000000;  /* 超出 int 存放範圍 */
    unsigned int sum = (unsigned int) (2000000000 + 2000000000);
    double f = 10 / 3;


    正確例子:
    /* 全部都用 unsigned int, 注意數字後面的 u, 大寫 U 也成 */
    unsigned int sum = 2000000000u + 2000000000u;

    /* 或是用顯式的轉型 */
    unsigned int sum = (unsigned int) 2000000000 + 2000000000;

    double f = 10.0 / 3.0;

    錯誤例子:
    unsigned int a = 0;
    int b[10];
    for(int i = 9 ; i >= a ; i--) {  b[i] = 0;  }

    說明:由於 int 與 unsigned 共同運算的時候,會提升 int 為 unsigned,
          因此迴圈條件永遠滿足,與預期行為不符


    錯誤例子:                                 (感謝 sekya 網友提供)
    unsigned char a = 0x80;   /* no problem */
    char b = 0x80;    /* implementation-defined result */
    if( b == 0x80 ) {        /* 不一定恒真 */
        printf( "b ok\n" );
    }

    說明:語言並未規定 char 天生為 unsigned 或 signed,因此將 0x80 放入
          char 型態的變數,將會視各家編譯器不同作法而有不同結果


08. 你不可以在一個運算式(expression)中,對一個基本型態的變數修改其值
    超過一次以上。否則,將導致未定義的行為(undefined behavior)

    錯誤例子:
    int i = 7;
    int j = ++i + i++;

    正確例子:
    int i = 7;
    int j = ++i;
    j += i++;

    你也不可以在一個運算式(expression)中,對一個基本型態的變數修改其值,
    而且還在同一個式子的其他地方為了其他目的而存取該變數的值。(其他目的,
    是指不是為了計算這個變數的新值的目的)。否則,將導致未定義的行為。


    錯誤例子:
    x = x++;


    錯誤例子:
    int arr[5];
    int i = 0;
    arr[i] = i++;

    正確例子:
    int arr[5];
    int i = 0;
    arr[i] = i;
    i++;

    錯誤例子:
    int i = 10;
    cout << i << "==" << i++;

    正確例子:
    int i = 10;
    cout << i << "==";
    cout << i++;

看板名稱: 確定(Enter) 取消(Esc) 搜尋(Space)
查詢帳號: 確定(Enter) 取消(Esc) 搜尋(Space)
搜尋: m)m文 b)進板 c)未分類 a)作者 /)標題 q)取消?[q]

搜尋 送出(Enter) 取消(Esc)

回覆文章至: f)看板 m)作者信箱 b)兩者皆是 q)取消?[f]
要引用原文嗎? y)引用原文 n)不引用 a)全部回覆 r)複製原文 q)取消?[y]
轉錄本文章於看板: 1)使用連結 2)使用複製 q)取消 ?[1]
轉寄至站內信箱於使用者: 確定(Enter) 取消(Esc)
轉寄至站內信箱於使用者: 確定(Enter) 取消(Esc)
修改文章標題為: 確定(Enter) 取消(Esc)
修改文章標題為: 確定(Enter) 取消(Esc) 全部(a)

確定要刪除這篇文章?(可按大U救回) 確定(Enter) 取消(Esc)

刪除理由:

確定(Enter) 取消(Esc)
加到這個分類: 確定(Enter) 下一層(→) 回上層(←) 取消(Esc)
你覺得這篇文章: 1)真讚 2)真瞎 q)取消?[1] (再選一次即可收回)
你覺得這篇文章: 1)值得推薦 2)表示反對 3)單純註解 q)取消?[3]
guest
預覽(Enter) 取消(Esc)
上傳圖片
按ctrl+Enter可輸入下一行。
guest
確定要送出? 確定(Enter) 取消(Esc) 繼續(e)
搜尋: 送出(Enter) 取消(Esc)

▏▎▍▌▋▊▉ 請按任意鍵繼續