2009年12月13日 星期日

指標常犯錯誤一

最近NJ在談指標, 我昨天剛犯了一個常見的指標錯誤, po來和大家分享, 順便秀一個po程式碼的語法.

我寫了個小routine要parse字串, 把"aaa_bbbb_cc"拆成"aaa", "bbbb", "cc", 其中"_"代表切分用的字元, 程式碼大概如下(忽略buffer size checking等細節)


void ParseString(char* pSource, char* pDest, const charcSeparator) {
    while(true) {
        if(*pSource == '\0' || *pSource == cSeparator)
            break;
        *pDest = *pSource;
        pDest++;
        pSource++;
    }
    *pDest = '\0';
}

void InCallerFunction() {
    char Buf[256];
    char* pSource = "String to be parsed";
    while(*pSource != '\0') {
        ParseString(pSource, Buf, ' '); //這裡用空白字元作切分
        DoSomeThing(Buf);
    }
}

Compile沒問題, 但一跑起來只有第一個字串被抓出來, 而且陷入無窮迴圈.

原來我想透過改變 pSource 所指到的位置, 指到結尾就結束 while loop, 但 pSource 沒有改變. 原因是我的 pSource 是以 call-by-value 的方式傳進去的. 這個錯誤很像下面這個版本:
void IncreaseI(int i) {
   i++;
}

void main() {
    int i = 0;
    IncreaseI(i);
    // i still == 0
}

main() 中的 i 不會被 IncreaseI() 中的 i++ 給改變, 因為 IncreaseI() 當中改變的只是 i 的分身. 應該改成:
void IncreaseI(int* i) {
   (*i)++;
}

所以要修正 ParseString 的問題只要改成傳"指向字串的指標的指標"進去即可:
void ParseString(char** ppSource, char* pDest, const charcSeparator) {
    char* pSource = *ppSource;
    while(true) {
        if(*pSource == '\0' || *pSource == cSeparator)
            break;
        *pDest = *pString;
        pDest++;
        pString++;
    }
    *pDest = '\0';
    *ppSource = pSource;
}

PS:裡面的字還有辦法加粗體或帶顏色的屬性嗎?

6 則留言:

  1. 我們都用 SyntaxHighlighter
    我暫時先幫你改成 => pre class="brush: cpp; wrap-lines: false;"

    回覆刪除
  2. 感謝分享! ,第一時間還真不容易看出來問題在哪(關鍵在邏輯上希望ParseString裡讓source pointer 一起跟著跑)。
    ps: ParseString裡的 pBuf 應該改成 pDest?

    回覆刪除
  3. Thanks James!

    Hi Abow, 是低 pBuf已改正為 pDest
    謝啦

    回覆刪除
  4. 作者已經移除這則留言。

    回覆刪除
  5. 之前在看 Refactoring 時, 有提到因為 Java 用的是 Pass by Value,
    所以作者, Martin Fowler, 認為這類傳進 method 的 parameter, 應該在 method 內用其他 temporarily variable 來接他的值, 之後 method 內都用 temporarily variable 以避免混淆和誤用. 甚至在 method 宣告, 該 parameter 也可以直接宣告成 final 來限制其使用行為.

    Item:
    Remove Assignments to Parameters

    回覆刪除