Goto, and intermixed declarations and statements
最近解了一個 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] 指令一併使用的時候。上面的範例程式,就有這樣的陷阱隱含著。
問題就發生在,因為 intermixed declarations and statements 的關係,變數在真的要被用到時,才被宣告,然而,並不是說變數在被宣告時,才真的存在。實際上 compiler 的內部運作是,在一進入函式時,就將所有函式變數所需要的空間(在 stack 上)配置好,然後直到程式執行到宣告處時,才將變數空間初始化。也就是說,變數的 allocation 與 initialization 被拆成兩步驟,allocation 在一開始就發生了,此時變數已存在,但要直到宣告時,才有實質,另外再加上,「要用的變數在之前已宣告過」的規則,宣告之後,才有名字可以指涉其所代表的空間。
如果,getenv() 失敗,如該環境變數不存在,此時便會在宣告 cfg_fp 之前,就 goto 到 err_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
gotostatement 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
switchstatement to acaselabel 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_FILE,goto到err_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_FILE,goto到err_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_FILE,goto到err_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_FILE,goto到err_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_FILE,goto到err_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."



6 Comments
這問題我也碰過,不過是在還沒真正造成 bug 前就被我發現了,是別的同事寫的 code,我因為本能的看到 goto 就反感了起來,因此還沒造成 bug 前就被我挑出來了。
我的原則是直接不用 goto,除非是臨時的測試用的 code,為了省事才有可能會去用。
咳咳~ goto 大概不是一無事處...
以下的 code, 如果有更有結構性, 更易懂, 更直觀的寫法的話, goto 的優點就又少一項了.
武器沒有好或壞之分,完全是看誰在使用,怎麼使用...
bool init_objects()
{
int* k1;
int* k2;
int* k3;
int* k4;
int* kN;
k1= new int;
if(NULL == k1) goto err_exit;
k2= new int;
if(NULL == k2) goto err_free_k1;
k3= new int;
if(NULL == k3) goto err_free_k2;
k4= new int;
if(NULL == k4) goto err_free_k3;
kN= new int;
if(NULL == kN) goto err_free_k4;
// do something you want.
// free all allocated object.
err_free_kN:
delete kN;
kN= NULL;
err_free_k4:
delete k4;
k4= NULL;
err_free_k3:
delete k3;
k3= NULL;
err_free_k2:
delete k2;
k2= NULL;
err_free_k1:
delete k1;
k1= NULL;
err_exit:
return 0;
}
Lemon,
要是在 /* do something you want. */ 裡面 throw 了怎麼辦?所以我們可以利用 boost::scoped_ptr 或是 std::auto_ptr,來達到「更有結構性、更易懂、更直觀」:
bool init_objects() { // boost::scoped_ptr guarantees delete holded pointer when destruct. // std::auto_ptr may suite here, too, depend on how we use it. boost::scoped_ptr<int> k1(new int); boost::scoped_ptr<int> k2(new int); boost::scoped_ptr<int> k3(new int); boost::scoped_ptr<int> k4(new int); boost::scoped_ptr<int> kN(new int); // do something you want. return 0; }哈哈哈哈, 慣C...
我的一個原則:能使用 smart pointer 就儘量用 smart pointer. 一般人只想到 leak 問題,所以覺得只要記得 delete/release resource 就行了,但如果在該 scope 有多個退出點時,很容易就漏掉,例如 Jeff 講的情況:exception.
另外就是,我比較喜歡用 Loki 的 smart pointer, 因為它跟本是萬用的,不只是 memory 可以靠他管理,COM 這種 add/release reference 的可以,就連其他資源管理也通通可以,例如 LoadLibrary/FreeLibray、network connection、反正任何一種需要生成/消滅或是初始/結束的,都可以用它。
剛剛把某專案拿回 VC6 編譯,發覺新加的功能,中了一堆這種陷阱。在 goto 滿天飛的程式裡加功能,還真是危險啊。
Post a Comment