最近解了一個 bug:在 intermixed declarations and statements 環境下,使用了 goto 指令,導致因為不正確的初始值,使程式執行了不正確的執行路徑,而把程式當掉。

在傳統的 C 程式裡,我們必須在 function 的一開頭,就將所有區域變數 (local/auto variables) 統統宣告好,然後才能夠開始寫第一個可以執行的 statements。不過,在 C++ 程式裡,以及從 C99 開始,我們可以將區域變數的宣告,與可執行的 statements,統統混在一起使用,只要遵守「要用的變數在之前已宣告過」的原則即可,這叫做 intermixed declarations and statements

例如以下的程式:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/**
 * Read configuration file where its file name specified by environment
 * variable CONFIG_FILE.
 *
 * @return Returns 1 when success, 0 on errors.
 */
int ReadConfigFile()
{
    int ok = 0; /* the final return value, initial to 0 which means errors */

    /*
     * Get config file name from environment variable CONFIG_FILE.
     */

    const char* cfg_file = getenv("CONFIG_FILE");
    if (NULL == cfg_file) {
        printf("ERROR: Missing environment variable: CONFIG_FILE\n");
        goto err_out;
    }

    /*
     * Open configuration file.
     */

    FILE* cfg_fp = fopen(cfg_file, "r");
    if (NULL == cfg_fp) {
        printf("ERROR: Failed to open config file: %s\n", cfg_file);
        goto err_out;
    }

    /*
     * Read and parse configuration file.
     */

    size_t line_no = 0;
    char   line[512];
    while (NULL != fgets(line, sizeof(line), cfg_fp)) {
        ++line_no;
        char name[512];
        char value[512];
        if (2 != sscanf(line, "%s = %s", &name, &value)) {
            printf("ERROR: Bad configuration format at line %d.\n", line_no);
            goto err_out;
        }
        /* ...save name-value pair configuration... */
        printf("Loaded config: %s = %s\n", name, value);
    }

    ok = 1; /* when we reach here, everything is ok */

err_out:

    /*
     * Close configuration file if opened.
     */

    if (NULL != cfg_fp) { /* if fopen() failed, cfg_fp would be NULL */
        fclose(cfg_fp);
    }

    return ok;
}

int main()
{
    return !ReadConfigFile();
}

在上面的程式裡,透過 intermixed declarations and statements,我們可以將變數「就近宣告」,所有的變數,只有在要被用到的時候,才宣告。這樣子,程式的可讀性較佳,且因為「要用的變數在之前已宣告過」的原則,使我們得以藉著「不過早宣告變數」,限縮變數的「可使用範圍(scope[1])」,減少誤用的機會。

雖然 intermixed declarations and statements 有這麼多的好處,但還是有陷阱,尤其是當與 goto[2] 指令一併使用的時候。上面的範例程式,就有這樣的陷阱隱含著。

講到這裡,不得不提的就是,「是否使用 goto」,其實是個爭議性頗高的議題,幾十年前就已經爭論不休了。包括 DijkstraKnuth 等大師,都寫過論文討論 goto 的使用。基本上,上面範例程式的 goto 用法,其實是慣 C 魔人們(不是我)為了要在 C 裡面模擬 try-catch 的機制:

  • 當有錯誤發生時,就 goto 到函式最後面的 err_out 處。此時 goto 相當於 try 的角色;
  • 於函式最後面的 err_out 處,利用變數是否仍為初值,來判斷是否有被使用過,以決定要不要釋放變數所代表、指涉的資源。此 err_out 處,相當於 catch 加上 final 區塊的角色。
  • 為了避免 goto 可能造成的邏輯混亂問題,限定使用 goto 時,只能夠「跳」到同一個函式內,並只能往後跳,不能往前跳。

我個人的偏好是,盡可能地不要使用 goto。以上例來說,在 function 裡 throw,同時在 function 裡 catch,實際上經過 optimization 之後,效能和 goto 差不多,但少了 goto 的缺點,並多了「destruct-when-throw」的優點,程式可以更加穩固。

問題就發生在,因為 intermixed declarations and statements 的關係,變數在真的要被用到時,才被宣告,然而,並不是說變數在被宣告時,才真的存在。實際上 compiler 的內部運作是,在一進入函式時,就將所有函式變數所需要的空間(在 stack 上)配置好,然後直到程式執行到宣告處時,才將變數空間初始化。也就是說,變數的 allocation 與 initialization 被拆成兩步驟,allocation 在一開始就發生了,此時變數已存在,但要直到宣告時,才有實質,另外再加上,「要用的變數在之前已宣告過」的規則,宣告之後,才有名字可以指涉其所代表的空間。

如果,getenv() 失敗,如該環境變數不存在,此時便會在宣告 cfg_fp 之前,就 gotoerr_out,然後檢查 cfg_fp 是否為 NULL 時,以決定要不要 fclose() 之。因為在一進入 ReadConfigFile(),所有的函式變數所佔用的空間,便已配置好, 然後又因為 err_out 位在 cfg_fp 宣告的後面(syntatically),所以可以使用 cfg_fp 這個變數。然而,實際上 cfg_fp 的宣告,被 goto 「跳」過去了,故此時的 cfg_fp,其實是個只有配置空間,但沒有經過初始化的變數。

對於函式內的區域變數(auto variables),依照 C/C++ 的規定,不會自動初始化為 0,可能是任意數值。所以,配有空間,但沒有經過初始化的 cfg_fp,其值有很大的機會,不為 0。結果就是,沒有經過 fopen()cfg_fp,被誤認為有開過檔,傳給 fclose() 導致 fclose() 收到不正確的 FILE* 指標,將程式當掉。

這種 goto 加上 intermixed declarations and statements 的陷阱,正常來說,實做 C/C++ compiler 的時候,一定會發現。所以,理論上 C/C++ 的標準制定者,應該也會意識到這個問題。所以我查了一下 C99 與 C++98 標準:

C99 6.8.6.1.4

EXAMPLE 2: A goto statement is not allowed to jump past any declarations of objects with variably modified types. A jump within the scope, however, is permitted.

goto lab3;            // invalid: going INTO scope of VLA.
{
    double a[n];
    a[j] = 4.4;
lab3:
    a[j] = 3.3;
    goto lab4;        // valid: goint WITHIN scope of VLA.
    a[j] = 5.5;
lab4:
    a[j] = 6.6;
}
goto lab4;            // invalid: going INTO scope of VLA.

上面註解裡的 VLA,是 variable-length array 的縮寫,這是 C99 新增的功能:run-time 決定長度的陣列 (C99 6.7.5.2.10)。不過,從本文一開始的範例來看,goto 的問題,不限於 VLA。

我手上沒有 C89、C90 的標準文件,無從查考。

C++98 6.7.3

It is possible to transfer into a block, but not in a way that bypass declarations with initialization. A program that jumps77) from a point where a local variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has POD type (3.9) and is declared without initializer (8.5).

[Example:

void f()
{
    // ...
    goto lx;       // ill-formed: jump into scope of a
    // ...
ly:
    X a = 1;
    // ...
1x:
    goto 1y;       // OK, jump implies destructor
                   // call for a followed by construction
                   // again immediately following label 1y
}

--end example]

77) The transfer from the condition of a switch statement to a case label is considered a jump in this respect.

由 C99 6.8.6.1.4 與 C++98 6.7.3 可知,在制定 C/C++ 時,已經將這個問題考慮進去了。既然 C/C++ 標準已經預防了這樣的情形,但 bug 還是碰到了,可見是 compiler 的實作,沒有遵照標準,發出 error,導致寫這段程式的人,踏入了陷阱。

所以,就來看看各家實作的狀況吧。

  • Visual C++ 6 on Windows, compiled as C
    SHELL> "%ProgramFiles%\Microsoft Visual Studio\VC98\Bin\VCVARS32.BAT"
    Setting environment for using Microsoft Visual C++ tools.
    SHELL> CL.exe ReadConfigFile.c
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86
    Copyright (C) Microsoft Corp 1984-1998. All rights reserved.
    
    ReadConfigFile.c
    ReadConfigFile.c(29) : error C2275: 'FILE' : illegal use of this type as an expression
            C:\PROGRA~1\MICROS~3\VC98\INCLUDE\stdio.h(156) : see declaration of 'FILE'
    ReadConfigFile.c(29) : error C2065: 'cfg_fp' : undeclared identifier
    ReadConfigFile.c(30) : warning C4047: '==' : 'void *' differs in levels of indirection from 'int '
    ReadConfigFile.c(39) : error C2275: 'size_t' : illegal use of this type as an expression
            C:\PROGRA~1\MICROS~3\VC98\INCLUDE\stdio.h(70) : see declaration of 'size_t'
    ReadConfigFile.c(39) : error C2146: syntax error : missing ';' before identifier 'line_no'
    ReadConfigFile.c(39) : error C2065: 'line_no' : undeclared identifier
    ReadConfigFile.c(40) : error C2143: syntax error : missing ';' before 'type'
    ReadConfigFile.c(41) : error C2065: 'line' : undeclared identifier
    ReadConfigFile.c(41) : warning C4047: 'function' : 'char *' differs in levels of indirection from 'int '
    ReadConfigFile.c(41) : warning C4024: 'fgets' : different types for formal and a ctual parameter 1
    ReadConfigFile.c(41) : warning C4047: 'function' : 'struct _iobuf *' differs in levels of indirection from 'int '
    ReadConfigFile.c(41) : warning C4024: 'fgets' : different types for formal and a ctual parameter 3
    ReadConfigFile.c(43) : error C2143: syntax error : missing ';' before 'type'
    ReadConfigFile.c(44) : error C2143: syntax error : missing ';' before 'type'
    ReadConfigFile.c(45) : warning C4047: 'function' : 'const char *' differs in levels of indirection from 'int '
    ReadConfigFile.c(45) : warning C4024: 'sscanf' : different types for formal and actual parameter 1
    ReadConfigFile.c(45) : error C2065: 'name' : undeclared identifier
    ReadConfigFile.c(45) : error C2065: 'value' : undeclared identifier
    ReadConfigFile.c(61) : warning C4047: '!=' : 'void *' differs in levels of indirection from 'int '
    ReadConfigFile.c(62) : warning C4047: 'function' : 'struct _iobuf *' differs in levels of indirection from 'int '
    ReadConfigFile.c(62) : warning C4024: 'fclose' : different types for formal and actual parameter 1
    

    從這堆 error message 可知,Visual C++ 6 以 C 模式編譯時,根本不支援 intermixed declarations and statements。

  • Visual C++ 6 on Windows, compiled as C++
    SHELL> "%ProgramFiles%\Microsoft Visual Studio\VC98\Bin\VCVARS32.BAT"
    Setting environment for using Microsoft Visual C++ tools.
    SHELL> CL.exe ReadConfigFile.cpp
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86
    Copyright (C) Microsoft Corp 1984-1998. All rights reserved.
    
    ReadConfigFile.cpp
    ReadConfigFile.cpp(55) : error C2362: initialization of 'line_no' is skipped by 'goto err_out'
            ReadConfigFile.cpp(39) : see declaration of 'line_no'
    ReadConfigFile.cpp(55) : error C2362: initialization of 'line_no' is skipped by 'goto err_out'
            ReadConfigFile.cpp(39) : see declaration of 'line_no'
    ReadConfigFile.cpp(55) : error C2362: initialization of 'cfg_fp' is skipped by 'goto err_out'
            ReadConfigFile.cpp(29) : see declaration of 'cfg_fp'
    

    以 C++ 模式編譯時,成功捕捉到了 goto 與 intermixed declarations and statements 的錯誤。

  • Visual C++ 8 on Windows, compiled as C
    SHELL> "%ProgramFiles%\Microsoft Visual Studio 8\VC\bin\vcvars32.bat"
    Setting environment for using Microsoft Visual Studio 2005 x86 tools.
    SHELL> CL.exe ReadConfigFile.c
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.762 for 80x86
    
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    ReadConfigFile.c
    ReadConfigFile.c(29) : error C2275: 'FILE' : illegal use of this type as an expression
            C:\Program Files\Microsoft Visual Studio 8\VC\INCLUDE\stdio.h(69) : see declaration of 'FILE'
    ReadConfigFile.c(29) : error C2065: 'cfg_fp' : undeclared identifier
    ReadConfigFile.c(30) : warning C4047: '==' : 'void *' differs in levels of indirection from 'int'
    ReadConfigFile.c(39) : error C2275: 'size_t' : illegal use of this type as an expression
            C:\Program Files\Microsoft Visual Studio 8\VC\INCLUDE\crtdefs.h(482) : see declaration of 'size_t'
    ReadConfigFile.c(39) : error C2146: syntax error : missing ';' before identifier 'line_no'
    ReadConfigFile.c(39) : error C2065: 'line_no' : undeclared identifier
    ReadConfigFile.c(40) : error C2143: syntax error : missing ';' before 'type'
    ReadConfigFile.c(41) : error C2065: 'line' : undeclared identifier
    ReadConfigFile.c(41) : warning C4047: 'function' : 'char *' differs in levels of indirection from 'int'
    ReadConfigFile.c(41) : warning C4024: 'fgets' : different types for formal and actual parameter 1
    ReadConfigFile.c(41) : warning C4047: 'function' : 'FILE *' differs in levels of indirection from 'int'
    ReadConfigFile.c(41) : warning C4024: 'fgets' : different types for formal and actual parameter 3
    ReadConfigFile.c(43) : error C2143: syntax error : missing ';' before 'type'
    ReadConfigFile.c(44) : error C2143: syntax error : missing ';' before 'type'
    ReadConfigFile.c(45) : warning C4047: 'function' : 'const char *' differs in levels of indirection from 'int'
    ReadConfigFile.c(45) : warning C4024: 'sscanf' : different types for formal and actual parameter 1
    ReadConfigFile.c(45) : error C2065: 'name' : undeclared identifier
    ReadConfigFile.c(45) : error C2065: 'value' : undeclared identifier
    ReadConfigFile.c(61) : warning C4047: '!=' : 'void *' differs in levels of indirection from 'int'
    ReadConfigFile.c(62) : warning C4047: 'function' : 'FILE *' differs in levels of indirection from 'int'
    ReadConfigFile.c(62) : warning C4024: 'fclose' : different types for formal and actual parameter 1
    

    從這堆 error message 可知,Visual C++ 8 以 C 模式編譯時,根本不支援 intermixed declarations and statements。

  • Visual C++ 8 on Windows, compiled as C++
    SHELL> "%ProgramFiles%\Microsoft Visual Studio 8\VC\bin\vcvars32.bat"
    Setting environment for using Microsoft Visual Studio 2005 x86 tools.
    SHELL> CL.exe ReadConfigFile.cpp
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.762 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    ReadConfigFile.cpp
    Microsoft (R) Incremental Linker Version 8.00.50727.762
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /out:ReadConfigFile.exe
    ReadConfigFile.obj
    SHELL> ReadConfigFile.exe
    ERROR: Missing environment variable: CONFIG_FILE
    (crashed)
    

    編譯成功,執行時找不到環境變數 CONFIG_FILEgotoerr_out,誤判 cfg_fp 呼叫了不應呼叫的 fclose,最後程式當掉了。

  • Visual C++ 9 on Windows, compiled as C
    SHELL> "%ProgramFiles%\Microsoft Visual Studio 9.0\VC\bin\vcvars32.bat"
    Setting environment for using Microsoft Visual Studio 2008 x86 tools.
    SHELL> CL.exe ReadConfigFile.c
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.30729.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    ReadConfigFile.c
    ReadConfigFile.c(29) : error C2275: 'FILE' : illegal use of this type as an expression
            C:\Program Files\Microsoft Visual Studio 9.0\VC\INCLUDE\stdio.h(69) : see declaration of 'FILE'
    ReadConfigFile.c(29) : error C2065: 'cfg_fp' : undeclared identifier
    ReadConfigFile.c(30) : error C2065: 'cfg_fp' : undeclared identifier
    ReadConfigFile.c(30) : warning C4047: '==' : 'void *' differs in levels of indirection from 'int'
    ReadConfigFile.c(39) : error C2275: 'size_t' : illegal use of this type as an expression
            c:\program files\microsoft visual studio 9.0\vc\include\codeanalysis\sourceannotations.h(19) : see declaration of 'size_t'
    ReadConfigFile.c(39) : error C2146: syntax error : missing ';' before identifier 'line_no'
    ReadConfigFile.c(39) : error C2065: 'line_no' : undeclared identifier
    ReadConfigFile.c(40) : error C2143: syntax error : missing ';' before 'type'
    ReadConfigFile.c(41) : error C2065: 'line' : undeclared identifier
    ReadConfigFile.c(41) : warning C4047: 'function' : 'char *' differs in levels of indirection from 'int'
    ReadConfigFile.c(41) : warning C4024: 'fgets' : different types for formal and actual parameter 1
    ReadConfigFile.c(41) : error C2065: 'line' : undeclared identifier
    ReadConfigFile.c(41) : error C2065: 'cfg_fp' : undeclared identifier
    ReadConfigFile.c(41) : warning C4047: 'function' : 'FILE *' differs in levels of indirection from 'int'
    ReadConfigFile.c(41) : warning C4024: 'fgets' : different types for formal and actual parameter 3
    ReadConfigFile.c(42) : error C2065: 'line_no' : undeclared identifier
    ReadConfigFile.c(43) : error C2143: syntax error : missing ';' before 'type'
    ReadConfigFile.c(44) : error C2143: syntax error : missing ';' before 'type'
    ReadConfigFile.c(45) : error C2065: 'line' : undeclared identifier
    ReadConfigFile.c(45) : warning C4047: 'function' : 'const char *' differs in levels of indirection from 'int'
    ReadConfigFile.c(45) : warning C4024: 'sscanf' : different types for formal and actual parameter 1
    ReadConfigFile.c(45) : error C2065: 'name' : undeclared identifier
    ReadConfigFile.c(45) : error C2065: 'value' : undeclared identifier
    ReadConfigFile.c(46) : error C2065: 'line_no' : undeclared identifier
    ReadConfigFile.c(50) : error C2065: 'name' : undeclared identifier
    ReadConfigFile.c(50) : error C2065: 'value' : undeclared identifier
    ReadConfigFile.c(61) : error C2065: 'cfg_fp' : undeclared identifier
    ReadConfigFile.c(61) : warning C4047: '!=' : 'void *' differs in levels of indirection from 'int'
    ReadConfigFile.c(62) : error C2065: 'cfg_fp' : undeclared identifier
    ReadConfigFile.c(62) : warning C4047: 'function' : 'FILE *' differs in levels of indirection from 'int'
    ReadConfigFile.c(62) : warning C4024: 'fclose' : different types for formal and actual parameter 1
    

    從這堆 error message 可知,Visual C++ 9 以 C 模式編譯時,根本不支援 intermixed declarations and statements。

  • Visual C++ 9 on Windows, compiled as C++
    SHELL> "%ProgramFiles%\Microsoft Visual Studio 9.0\VC\bin\vcvars32.bat"
    Setting environment for using Microsoft Visual Studio 2008 x86 tools.
    SHELL> CL.exe ReadConfigFile.cpp
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.30729.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    ReadConfigFile.cpp
    Microsoft (R) Incremental Linker Version 9.00.30729.01
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /out:ReadConfigFile.exe
    ReadConfigFile.obj
    
    SHELL> ReadConfigFile.exe
    ERROR: Missing environment variable: CONFIG_FILE
    (crashed)
    

    編譯成功,執行時找不到環境變數 CONFIG_FILEgotoerr_out,誤判 cfg_fp 呼叫了不應呼叫的 fclose,最後程式當掉了。

  • GCC 4.2.1 on FreeBSD, compiled as C (gnu89)
    SHELL> gcc ReadConfigFile.c
    SHELL> a.out
    ERROR: Missing environment variable: CONFIG_FILE
    Bus error (core dumped)
    Exit 138

    編譯成功,執行時找不到環境變數 CONFIG_FILEgotoerr_out,誤判 cfg_fp 呼叫了不應呼叫的 fclose,最後程式當掉了。

  • GCC 4.2.1 on FreeBSD, compiled as C++ (gnu++98)
    SHELL> g++ ReadConfigFile.cpp
    goto_intermixed.cpp: In function 'int ReadConfigFile()':
    goto_intermixed.cpp:55: error: jump to label 'err_out'
    goto_intermixed.cpp:32: error:   from here
    goto_intermixed.cpp:39: error:   crosses initialization of 'size_t line_no'
    goto_intermixed.cpp:55: error: jump to label 'err_out'
    goto_intermixed.cpp:22: error:   from here
    goto_intermixed.cpp:39: error:   crosses initialization of 'size_t line_no'
    goto_intermixed.cpp:29: error:   crosses initialization of 'FILE* cfg_fp'
    Exit 1

    以 C++ 模式編譯時,成功捕捉到了 goto 與 intermixed declarations and statements 的錯誤。

  • GCC 4.2.1 on FreeBSD, compiled as C (c89)
    SHELL> gcc -pedantic -std=c89 ReadConfigFile.c
    ReadConfigFile.c: In function 'ReadConfigFile':
    ReadConfigFile.c:29: warning: ISO C90 forbids mixed declarations and code
    ReadConfigFile.c:39: warning: ISO C90 forbids mixed declarations and code
    ReadConfigFile.c:43: warning: ISO C90 forbids mixed declarations and code
    

    編譯成功,執行時找不到環境變數 CONFIG_FILEgotoerr_out,誤判 cfg_fp 呼叫了不應呼叫的 fclose,最後程式當掉了。

  • GCC 4.2.1 on FreeBSD, compiled as C (c99)
    SHELL> gcc -pedantic -std=c99 ReadConfigFile.c
    SHELL> ./a.out
    ERROR: Missing environment variable: CONFIG_FILE
    Bus error (core dumped)
    Exit 138
    

    編譯成功,執行時找不到環境變數 CONFIG_FILEgotoerr_out,誤判 cfg_fp 呼叫了不應呼叫的 fclose,最後程式當掉了。

  • GCC 4.2.1 on FreeBSD, compiled as C++ (c++98)
    SHELL> g++ -pedantic -std=c++98 ReadConfigFile.cpp
    ReadConfigFile.cpp: In function 'int ReadConfigFile()':
    ReadConfigFile.cpp:55: error: jump to label 'err_out'
    ReadConfigFile.cpp:32: error:   from here
    ReadConfigFile.cpp:39: error:   crosses initialization of 'size_t line_no'
    ReadConfigFile.cpp:55: error: jump to label 'err_out'
    ReadConfigFile.cpp:22: error:   from here
    ReadConfigFile.cpp:39: error:   crosses initialization of 'size_t line_no'
    ReadConfigFile.cpp:29: error:   crosses initialization of 'FILE* cfg_fp'
    Exit 1

    以 C++ 模式編譯時,成功捕捉到了 goto 與 intermixed declarations and statements 的錯誤。

總結以上各種編譯器與編譯組態,我們可以得到下列這張表:

編譯器 語言標準 編譯時正確產生錯誤訊息?
(「-」表示不支援 intermixed declarations and statements)
Visual C++ 6 C -
Visual C++ 6 C++
Visual C++ 8 C -
Visual C++ 8 C++
Visual C++ 9 C -
Visual C++ 9 C++
GCC 4.2.1 C
GCC 4.2.1 C++
GCC 4.2.1 C89 -
GCC 4.2.1 C99
GCC 4.2.1 C++98

從此表以及相關的錯誤訊息,我們可以知道以下兩件事情:

  • Visual C++ 6 是對的,從 Visual C++ 8 以後開始[3],就爛掉了。
  • GCC 4 的 C++ 模式是對的,但 C 模式只有在 C89 時,因為遵照標準不支援 intermixed declarations and statements 而避開了這個問題,否則都是爛的。

結論就是,基本上這個問題,我們 programmer 只能自求多福,自力救濟想辦法避免踩到陷阱,因為 compiler 幫不了多少忙。正規的作法之一,是努力提昇 test coverage,只要有測試到這條錯誤的執行路徑,幾乎 100% 可以把程式當掉,要解決就容易多了。另外就是,養成寫程式的好習慣:「變數要初始化」,以及「不要用 goto」。

補充資料:

  • ISO C Standard Update - VC team 的人報告了 C1x 的最新進展,文末提了一下 VC 對 C99 的支援問題,希望大家多提些意見,因為 "we don’t hear much from our C users"。然後就被一面倒地痛批,底下全是要求支援 C99 的 comments,有位 Tor[4] 還說 "these days I really wonder why we should continue to simulate our embedded systems under Windows, with the lack of attention MS has been showing ISO C99."

  1. Wikipedia: Scope, the range in which a variable can be referenced.
  2. Wikipedia: Goto, or go to, a branching construct in programming languages.
  3. 我手頭沒有Visual C++ 7 與 Visual C++ 7.1 可以測。
  4. 可能是經由 Tor 來的訪客,故也許不是真名。