[英]Best practices for non-local exit with cleanup in C?
什么是中止 C 錯誤的最佳實踐?
在我們的代碼庫中,我們目前有一個使用模式
#define CHECKERROR(code) if(code) { return code; }
但這會導致資源沒有在表單的代碼中關閉
/* not actual code due to non-disclosure restrictions */
int somefunction() {
handle_t res1, res2;
int errorcode;
res1 = getResource();
res2 = getResource();
errorcode = action1(res1, res2);
CHECK(errorcode);
errorcode = action2(res1, res2);
CHECK(errorcode);
freeResource(res1);
freeResource(res2);
return errorcode;
}
我遇到了模式
/* initialize resources */
do {
/* ... */
errorcode = action();
if(errorcode) break;
/* ... */
} while(0);
/* cleanup resources */
return errorcode;
以前在文章中,但現在找不到任何討論它的來源。
什么是一個好的做法,這將被認為是 C 的慣用做法? do { } while(0);
模式合格? 有沒有一種慣用的方法讓它更清楚,它不是一個循環,而是一個具有非本地退出的塊?
什么是中止 C 錯誤的最佳實踐?
什么是一個好的做法,這將被認為是 C 的慣用做法?
真的,什么都沒有。 沒有最佳實踐。 最好是針對您正在處理的特定案例量身定制特定的解決方案。 當然 - 專注於編寫可讀的代碼。
讓我們提到一些文件。 MISRA 2008 有以下內容。 規則很嚴格 -單一出口點。 所以你必須分配變量並跳轉到單個return
語句
規則 6–6–5(必需)function 應在 function 的末端有一個出口。
錯誤處理是唯一真正鼓勵使用goto
的地方。 Linux Kernel 編碼風格呈現並鼓勵使用goto
來“保持所有出口點關閉”。 該樣式未強制執行 - 並非所有 kernel 函數都使用此樣式。 參見Linux kernel 編碼風格 # 集中退出功能。
goto
的kernel推薦被SEI-C: MEM12-C采用。 在使用和釋放資源時讓 function 出錯時,請考慮使用 goto 鏈。
do { } while(0); 模式合格?
當然,為什么不呢。 如果你沒有在do {.. here.. }while(0)
塊內分配更多資源,你不妨編寫一個單獨的 function 然后從中調用return
。
這個想法也有擴展。 甚至使用longjmp
實現 C 中的異常。 我知道ThrowTheSwitch/CException 。
總的來說,C 中的錯誤處理並不容易。 處理來自多個庫的錯誤變得非常困難,並且是一門藝術。 請參閱MBed OS 錯誤處理、 mbed_error.h ,甚至是解釋 MBed OS 錯誤代碼的網站。
非常喜歡函數中的單個返回點 - 正如您發現的那樣,使用您的CHECK(errorcode);
會泄露資源。 多個return
地點令人困惑。 考慮使用goto
s:
int somefunction() {
int errorcode = 0;
handle_t res1 = getResource();
if (!res1) {
errorcode = somethnig;
goto res1_fail;
}
handle_t res2 = getResource();
if (!res2) {
errorcode = somethnig_else;
goto res2_fail;
}
errorcode = action1(res1, res2);
if (!errorcode) {
goto actions_fail;
}
errorcode = action2(res1, res2);
if (!errorcode) {
goto actions_fail;
}
actions_fail:
freeResource(res2);
res2_fail:
freeResource(res1);
res1_fail:
return errorcode;
}
首先,隱藏流控制的神秘宏(例如CHECKERROR
)被廣泛認為是非常糟糕的做法。 不要那樣做 - 創建其他 C 程序員無法理解的秘密宏語言是比代碼重復更嚴重的質量問題。 代碼重復不好,但不應該通過制造更糟糕的問題來解決。 假設讀者非常了解 C,但不要假設他們知道或想知道您在該項目本地的秘密宏語言。
在慣用的 C 中有兩種可接受的方式來編寫此代碼。 使用顯式return
或使用“on error goto”模式à la BASIC。 我通常會推薦return
版本,因為它可以讓你避免再次進行那種令人厭煩的“ goto
被認為有害”的辯論。 但是在 function 的末尾進行清理也是可以接受的,只要你只向下跳。
(您的do-while(0)
與break
只是變相的goto
。它沒有好壞之分。)
函數的單點return
也存在爭議,尤其是在 MISRA-C 的上下文中(請參閱this )。 然而,從function多次返回是可以的,只要它不會使代碼更難閱讀。 在實踐中,這意味着您應該避免從深度嵌套的循環或語句中return
(或goto
)。 通常將“圈復雜度”(函數中可能的執行路徑的數量)保持在盡可能低的水平。
如果您需要釋放資源,我個人更喜歡return
而不是goto
。 為了return
,您需要制作一個包裝器 function,它還用於將資源分配與算法分離。 我會像這樣重寫你的代碼:
typedef enum // use an actual enum not sloppy int
{
OK, // keeping code 0 for no error is the most common practice
ERR_THIS,
ERR_THAT
} err_t;
static err_t the_actual_algorithm (handle_t res1, handle_t res2) // likely inlined
{
err_t errorcode;
errorcode = action1(res1, res2);
if(errorcode != OK) { return errorcode; }
errorcode = action2(res1, res2);
if(errorcode != OK) { return errorcode; }
return OK;
}
err_t somefunction (void) // note void, not empty parenthesis which is obsolete style
{
handle_t res1, res2;
err_t errorcode;
res1 = getResource();
res2 = getResource();
errorcode = the_actual_algorithm(res1, res2);
freeResource(res1);
freeResource(res2);
return errorcode;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.