避免在C错误处理中重复

时间:2017-09-07 18:06:18

标签: c gcc error-handling c-preprocessor

我经常编写的代码最终是长序列,如

int error;

error = do_something();
if (error) {
    return error;
}

error = do_something_else(with, some, args);
if (error) {
    return error;
}


error = do_something_yet_again();
if (error) {
    return error;
}

return 0;

我正在寻找一种更清晰的方式来写这个,这在某种程度上避免了重复的相同检查。到目前为止,我已经编写了一个ERROR_OR宏,其工作方式类似于

#define ERROR_OR(origerr, newerr)           \
    ({                                      \
        int __error_or_origerr = (origerr); \
        (__error_or_origerr != 0)           \
                ? __error_or_origerr        \
                : (newerr);                 \
    })

允许原始代码变为类似

int error = 0;

error = ERROR_OR(error, do_something());
error = ERROR_OR(error, do_something_else(with, some, args));
error = ERROR_OR(error, do_something_yet_again());

return error;

这(在我看来)有点清洁。它也不太容易理解,因为除非您阅读其文档和/或实现,否则ERROR_PRESERVE宏的功能不明显。它也没有解决重复的问题,只是更容易在一行上编写所有(现在隐式)检查。

我真的想重写这一切,如下所示:

return ERROR_SHORT_CIRCUIT(
    do_something(),
    do_something_else(with, some, args),
    do_something_yet_again()
);

假设的ERROR_SHORT_CIRCUIT

  • 在其参数列表中使用可变数量的表达式
  • 按顺序评估每个表达
  • 如果每个表达式的计算结果为零,则评估为零本身
  • 如果任何表达式的计算结果为非零,则立即终止并计算为该最后一个表达式的值

最后一个条件是我的短路与||运算符的直接使用不同 - 因为这将评估为1而不是误差值。

我最初的写作尝试如下:

#define ERROR_SHORT_CIRCUIT(firsterr, ...)          \
    ({                                              \
        int __error_ss_firsterr = (firsterr);       \
        (__error_ss_firsterr != ERROR_NONE)         \
                ? __error_ss_firsterr               \
                : ERROR_SHORT_CIRCUIT(__VA_ARGS__); \
    })

这有两个明显的问题:

  • 它不处理其基本情况(当__VA_ARGS__是单个值时)
  • C不支持递归宏

我已经研究了一些recursive macro hacks,但我不喜欢使用那种程度的预处理器魔法 - 太多的空间让某些东西变得微不足道。我也考虑过使用真实的(可能是可变的)函数,但这需要

  • 放弃短路行为
  • 将函数作为指针传递,从而规范化其签名

并且这两者似乎都比原始的显式代码更糟糕。

我很想听听有关处理此问题的最佳方法的建议。我对许多不同的方法持开放态度,但我的最终目标是在不损害可读性的情况下避免重复。

(我认为很明显我对像Ruby这样的语言中||运算符的行为感到羡慕。)

2 个答案:

答案 0 :(得分:4)

我使用的代码如下:

if ((error = do_something()) != 0 ||
    (error = do_something_else(with, some, args)) != 0 ||
    (error = do_something_yet_again()) != 0)
    return error;
return 0;

它是完全定义的,因为每个||运算符之前都有序列点。它并不真正需要一个宏。它只会在您在函数调用之间分配资源或执行其他操作时遇到问题,但这与示例代码显示的不同。至少90%的战斗是创建do_something_or_other()函数序列,以便于处理错误排序。

答案 1 :(得分:2)

另一种选择:

int error = 0;
do {
    // Note: extra parens suppress assignment-as-conditional warning

    if ((error = do_something())) break;
    if ((error = do_something_else())) break;
    if ((error = do_yet_another_thing())) break;
    error = do_a_final_thing();
} while (0);
return error;