简体   繁体   English

在 C 中进行清理的非本地退出的最佳实践?

[英]Best practices for non-local exit with cleanup in C?

What is considered best practice for aborting on errors in C?什么是中止 C 错误的最佳实践?

In our code base we currently have a pattern using在我们的代码库中,我们目前有一个使用模式

#define CHECKERROR(code) if(code) { return code; }

but this leads to resources not being closed in code of the form但这会导致资源没有在表单的代码中关闭

/* 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;
}

I came across the pattern我遇到了模式

/* initialize resources */
do {
    /* ... */
    errorcode = action();
    if(errorcode) break;
    /* ... */
} while(0);
/* cleanup resources */
return errorcode;

before in articles, but couldn't find any source discussing it now.以前在文章中,但现在找不到任何讨论它的来源。

What is a good practice, that would be considered idiomatic to C?什么是一个好的做法,这将被认为是 C 的惯用做法? Does the do { } while(0); do { } while(0); pattern qualify?模式合格? Is there an idiomatic way to make it more clear, that it is not intended to be a loop, but a block with non-local exit?有没有一种惯用的方法让它更清楚,它不是一个循环,而是一个具有非本地退出的块?

What is considered best practice for aborting on errors in C?什么是中止 C 错误的最佳实践?

What is a good practice, that would be considered idiomatic to C?什么是一个好的做法,这将被认为是 C 的惯用做法?

Really, nothing.真的,什么都没有。 There is no best-practice.没有最佳实践。 Best is to tailor a specific solution to the specific case you are handling.最好是针对您正在处理的特定案例量身定制特定的解决方案。 For sure - concentrate on writing readable code.当然 - 专注于编写可读的代码。

Let's mention some documents.让我们提到一些文件。 MISRA 2008 has the following. MISRA 2008 有以下内容。 The rule is strict - single exit point.规则很严格 -单一出口点。 So you have to assign variables and jump to a single return statement所以你必须分配变量并跳转到单个return语句

Rule 6–6–5 (Required) A function shall have a single point of exit at the end of the function.规则 6–6–5(必需)function 应在 function 的末端有一个出口。

Error handling is the only place where using goto is actually encouraged.错误处理是唯一真正鼓励使用goto的地方。 Linux Kernel Coding style presents and encourages using goto to "keep all exit points close". Linux Kernel 编码风格呈现并鼓励使用goto来“保持所有出口点关闭”。 The style is not enforced - not all kernel functions use this.该样式未强制执行 - 并非所有 kernel 函数都使用此样式。 See Linux kernel coding style # Centralized exiting of functions .参见Linux kernel 编码风格 # 集中退出功能

The kernel recommendation of goto was adopted by SEI-C: MEM12-C. goto的kernel推荐被SEI-C: MEM12-C采用。 Consider using a goto chain when leaving a function on error when using and releasing resources . 在使用和释放资源时让 function 出错时,请考虑使用 goto 链

Does the do { } while(0); do { } while(0); pattern qualify?模式合格?

Sure, why not.当然,为什么不呢。 If you do not allocate any more resources inside the do {.. here.. }while(0) block, you might as well write a separate function and then call return from it.如果你没有在do {.. here.. }while(0)块内分配更多资源,你不妨编写一个单独的 function 然后从中调用return

There are also expansions on the idea.这个想法也有扩展。 Even implementations of exceptions in C using longjmp .甚至使用longjmp实现 C 中的异常。 I know of ThrowTheSwitch/CException .我知道ThrowTheSwitch/CException

Overall, error handling in C is not easy.总的来说,C 中的错误处理并不容易。 Handling errors from multiple libraries becomes extremely hard and is an art of its own.处理来自多个库的错误变得非常困难,并且是一门艺术。 SeeMBed OS error-handling , mbed_error.h , even a site that explains MBed OS error codes .请参阅MBed OS 错误处理mbed_error.h ,甚至是解释 MBed OS 错误代码的网站

Strongly prefer single return point from your functions - as you found out, using your CHECK(errorcode);非常喜欢函数中的单个返回点 - 正如您发现的那样,使用您的CHECK(errorcode); will leak resources.会泄露资源。 Multiple return places are confusing.多个return地点令人困惑。 Consider using goto s:考虑使用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;
}

First of all, mysterious macros such as your CHECKERROR which hide away flow control are widely considered very bad practice.首先,隐藏流控制的神秘宏(例如CHECKERROR )被广泛认为是非常糟糕的做法。 Don't do that - creating secret macro languages that no other C programmer understands is a much more serious quality concern than code repetition.不要那样做 - 创建其他 C 程序员无法理解的秘密宏语言是比代码重复更严重的质量问题。 Code repetition isn't good but it shouldn't be solved by creating a much worse problem.代码重复不好,但不应该通过制造更糟糕的问题来解决。 Assume that the reader knows C well, but don't assume that they know or want to know your secret macro language local to this project.假设读者非常了解 C,但不要假设他们知道或想知道您在该项目本地的秘密宏语言。

In idiomatic C there are two acceptable ways to write this code.在惯用的 C 中有两种可接受的方式来编写此代码。 Either with explicit return or with the "on error goto" pattern à la BASIC.使用显式return或使用“on error goto”模式à la BASIC。 I would generally recommend the return version since it saves you from having that old tiresome " goto considered harmful" debate yet again.我通常会推荐return版本,因为它可以让你避免再次进行那种令人厌烦的“ goto被认为有害”的辩论。 But goto to a clean-up at the end of the function is acceptable too, as long as you only jump downwards.但是在 function 的末尾进行清理也是可以接受的,只要你只向下跳。

(Your do-while(0) with break is just a goto in disguise. It isn't better or worse.) (您的do-while(0)break只是变相的goto 。它没有好坏之分。)

The single point of return from functions is also debated, especially in the context of MISRA-C (see this ).函数的单点return也存在争议,尤其是在 MISRA-C 的上下文中(请参阅this )。 Multiple returns from a function is however fine as long as it doesn't make the code harder to read.然而,从function多次返回是可以的,只要它不会使代码更难阅读。 In practice this means that you should avoid return (or goto ) from inside deeply nested loops or statements.在实践中,这意味着您应该避免从深度嵌套的循环或语句中return (或goto )。 Generally keep the "cyclomatic complexity" (the number of possible execution paths in a function) as low as possible.通常将“圈复杂度”(函数中可能的执行路径的数量)保持在尽可能低的水平。

In case you need to free up resources, I personally prefer return over goto .如果您需要释放资源,我个人更喜欢return而不是goto For return you need to make a wrapper function, which also serves the purpose of separating resource allocation from the algorithm.为了return ,您需要制作一个包装器 function,它还用于将资源分配与算法分离。 I would have rewritten your code like this:我会像这样重写你的代码:

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