簡體   English   中英

在 C 中進行清理的非本地退出的最佳實踐?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM