在 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);
模式是否符合条件?有没有一种惯用的方法可以使它更清楚,它 不是 打算成为一个循环,而是一个具有非本地出口的块?
What is considered best practice for aborting on errors in C?
What is a good practice, that would be considered idiomatic to C?
真的,没什么。没有最佳实践。最好是针对您正在处理的具体案例量身定制具体的解决方案。当然 - 专注于编写可读代码。
让我们提及一些文件。 MISRA2008年有以下内容。规则很严格 - 单个 退出点。所以你必须分配变量并跳转到单个 return
语句
Rule 6–6–5 (Required) A function shall have a single point of exit at
the end of the function.
错误处理是唯一真正鼓励使用 goto
的地方。 Linux 内核编码风格提出并鼓励使用 goto
来“保持所有退出点关闭”。风格不是强制的——不是所有的内核函数都使用它。参见 Linux kernel coding style # Centralized exiting of functions。
SEI-C采纳了goto
的内核推荐:MEM12-C. Consider using a goto chain when leaving a function on error when using and releasing resources。
Does the do { } while(0); pattern qualify?
当然可以,为什么不呢。如果您不在 do { .. here .. }while(0)
块内分配更多资源,您不妨编写一个单独的函数,然后从中调用 return
。
这个想法也有扩展。甚至使用 longjmp
在 C 中实现异常。我知道 ThrowTheSwitch/CException。
总的来说,C 中的错误处理并不容易。处理来自多个库的错误变得极其困难,并且本身就是一门艺术。参见 MBed OS error-handling, mbed_error.h, even a site that explains MBed OS error codes。
强烈推荐您的函数中的单个 return 点 - 正如您发现的那样,使用您的 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 程序员无法理解的秘密宏语言是一个比代码重复更严重的质量问题。代码重复不好,但不应该通过制造更糟糕的问题来解决。假设 reader 非常了解 C,但不要假设他们知道或不想知道您在该项目本地使用的秘密宏语言。
在惯用的 C 中,有两种可接受的方式来编写此代码。使用显式 return
或使用 BASIC 中的“出错时转到”模式。我通常会推荐 return
版本,因为它可以让您避免再次进行令人厌烦的“goto
被认为有害”的争论。但是在函数末尾转到清理也是可以接受的,只要你只向下跳。
(你的do-while(0)
和break
只是变相的goto
,没有好坏之分。)
函数中 return
的单点也存在争议,尤其是在 MISRA-C 的上下文中(参见 )。然而,函数中的多个 returns fine 只要它不会使代码更难阅读。实际上,这意味着您应该避免在深度嵌套的循环或语句中使用 return
(或 goto
)。通常保持“循环复杂度”(函数中可能的执行路径数)尽可能低。
如果您需要释放资源,我个人更喜欢 return
而不是 goto
。对于 return
你需要做一个包装函数,这也起到了将资源分配与算法分离的目的。我会像这样重写你的代码:
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;
}
在 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);
模式是否符合条件?有没有一种惯用的方法可以使它更清楚,它 不是 打算成为一个循环,而是一个具有非本地出口的块?
What is considered best practice for aborting on errors in C?
What is a good practice, that would be considered idiomatic to C?
真的,没什么。没有最佳实践。最好是针对您正在处理的具体案例量身定制具体的解决方案。当然 - 专注于编写可读代码。
让我们提及一些文件。 MISRA2008年有以下内容。规则很严格 - 单个 退出点。所以你必须分配变量并跳转到单个 return
语句
Rule 6–6–5 (Required) A function shall have a single point of exit at the end of the function.
错误处理是唯一真正鼓励使用 goto
的地方。 Linux 内核编码风格提出并鼓励使用 goto
来“保持所有退出点关闭”。风格不是强制的——不是所有的内核函数都使用它。参见 Linux kernel coding style # Centralized exiting of functions。
SEI-C采纳了goto
的内核推荐:MEM12-C. Consider using a goto chain when leaving a function on error when using and releasing resources。
Does the do { } while(0); pattern qualify?
当然可以,为什么不呢。如果您不在 do { .. here .. }while(0)
块内分配更多资源,您不妨编写一个单独的函数,然后从中调用 return
。
这个想法也有扩展。甚至使用 longjmp
在 C 中实现异常。我知道 ThrowTheSwitch/CException。
总的来说,C 中的错误处理并不容易。处理来自多个库的错误变得极其困难,并且本身就是一门艺术。参见 MBed OS error-handling, mbed_error.h, even a site that explains MBed OS error codes。
强烈推荐您的函数中的单个 return 点 - 正如您发现的那样,使用您的 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 程序员无法理解的秘密宏语言是一个比代码重复更严重的质量问题。代码重复不好,但不应该通过制造更糟糕的问题来解决。假设 reader 非常了解 C,但不要假设他们知道或不想知道您在该项目本地使用的秘密宏语言。
在惯用的 C 中,有两种可接受的方式来编写此代码。使用显式 return
或使用 BASIC 中的“出错时转到”模式。我通常会推荐 return
版本,因为它可以让您避免再次进行令人厌烦的“goto
被认为有害”的争论。但是在函数末尾转到清理也是可以接受的,只要你只向下跳。
(你的do-while(0)
和break
只是变相的goto
,没有好坏之分。)
函数中 return
的单点也存在争议,尤其是在 MISRA-C 的上下文中(参见 return
(或 goto
)。通常保持“循环复杂度”(函数中可能的执行路径数)尽可能低。
如果您需要释放资源,我个人更喜欢 return
而不是 goto
。对于 return
你需要做一个包装函数,这也起到了将资源分配与算法分离的目的。我会像这样重写你的代码:
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;
}