【问题标题】:Pattern to prevent continually checking error?防止不断检查错误的模式?
【发布时间】:2014-08-30 17:58:44
【问题描述】:

在 C 语言中,是否有一种模式可以消除在调用其他函数的函数中不断检查错误的需要?

例如如果函数 foo() 依次调用 a()、b() 和 c(),则必须在继续之前检查每个函数的返回值。如果 a()、b() 或 c() 还调用其他函数,这些函数也可能调用其他函数,这会留下一长串错误检查,可能是相同的错误...

int16_t a(x_t x) {return DO_SOME_WORK(x);}

int16_t b(y_t y) {return DO_OTHER_WORK(y);}

int16_t c(z_t z) {return DO_MORE_WORk(z);}

foo (x_t x, y_t y, z_t z) {
    int16_t err = 0;
    // I can handle errors returned by either:
    err = a(x));
    if (err) return err;
    err = b(y);
    if (err) return err;
    err = c(z); 
    return err;

    // Or
    err = a(x);
    if (!err) err = b(y);
    if (!err) err = c(z);
    return err;
}

我更喜欢第二种方法,因为它有一个明确的单一退出点,但另一种方法是本地化处理,并且任何一种方法都引入了很多 if,对于大型程序,这需要很多额外的代码,尤其是在什么都不做的函数中但例如调用其他函数并在它们之间传递输出。 谢谢

【问题讨论】:

  • 我认为唯一其他真正的方法是通过setjmplongjmp“模拟异常”,但是您的程序流程变得不那么容易遵循了。此外,一种常见的编译器优化是将所有这些 return err 语句合并为一个,并将条件分支插入该语句。
  • err 的值是 0 和 1,还是有许多不同的 err 值?根据答案,可以使用不同的成语。
  • @chux 有许多不同的 err 值,在最终返回的上层函数中,它们被推送到错误队列以报告给父系统。

标签: c error-handling embedded


【解决方案1】:

如果函数的每个函数调用都需要错误检查,那么每次调用该函数时都应该检查错误。原因是即使它是相同的函数,输入不同并且全局(和静态局部)变量也不同,从而改变了结果。这意味着,如果您想检查错误,您实际上并没有浪费计算工作。

编辑:我不知道这是否明智,但您可以使用宏来清理语法

err = a(x);
if(err) return err;

看起来像

#define error_check(f, e) \
e = f; \
if(e) return e;

所以在你的代码中你可以写

error_check(a(x),err)

我没有使用宏的经验,但这应该可以。

【讨论】:

  • 这个想法非常好:“对于每个函数调用,参数或全局变量可以有不同的值;所以,如果你想检查错误,你不会浪费工作”。真的,非常好。
【解决方案2】:

在这种情况下,您可以利用 && 的短路求值,其中当左侧操作数为 false 时,不会评估右侧操作数。

如果您可能希望在对a()b()c() 的调用之间执行其他处理,则可以使用以下方法:

bool foo (x_t x, y_t y, z_t z) 
{
    bool err = a( x );
    err = err && b( y ) ;
    err = err && c( z ) ; 

    return err;
}

而如果函数仅由调用 a()b()c() 组成,那么您可以使用更简洁的:

bool foo (x_t x, y_t y, z_t z) 
{
    return a( x ) && 
           b( y ) && 
           c( z ) ; 
}

[根据评论添加]

解决@kkrambo关于函数返回的任何数值不传播给调用者的观点可以解决,但作为解决方案的吸引力越来越小:

int16_t foo (x_t x, y_t y, z_t z) 
{
    int16_t ret ;

    bool err = (ret = a( x ));
    err = err && (ret = b( y )) ;
    err = err && (ret = c( z )) ; 

    return ret ;
}

int16_t foo (x_t x, y_t y, z_t z) 
{
    uint16_t ret ;

    (ret = a( x )) && 
    (ret = b( y )) && 
    (ret = c( z )) ;

    return ret ; 
}

你甚至可以做的越来越可怕:

int16_t foo (x_t x, y_t y, z_t z) 
{
    int16_t err ;

    if( (err = a(x)) ) {}
    else if( (err = b(y)) ) {} 
    else if( (err = c(z)) ) {}

    return err ; 
}

简化为以下err 为布尔值的情况:

bool foo (x_t x, y_t y, z_t z) 
{
    bool err = true ;

    if( a(x) ) {}
    else if( b(y) ) {} 
    else if( c(z) ) {}
    else { err = false }

    return err ; 
}

【讨论】:

  • 技术上你可以这样做,但是下一个维护你的代码的人会讨厌你。 :)
  • 在原始问题中,函数在成功时返回 0 或可以识别各种错误情况的非零值。但是对于您的建议,函数必须在成功时返回 true,或者对于所有可能的错误返回 false。任何描述性错误代码都会丢失。
  • @AndrewCottrell :代码的可取性是一个不同的问题。我认为从这个角度来看,第一种模式比第二种模式更可取,但会反复评估err。简洁和“聪明”并不总是更好。
  • @kkrambo :确实如此,但在示例中,返回值仅用作 foo() 中的布尔值,尽管 err 的任何数值都会传播并返回给 @ 的调用者987654339@,因此此代码可能不等效,可能无法满足所有要求。
【解决方案3】:

由于它的目标是返回错误代码而不是简单的布尔值,因此 OP 提供的 2 种方法至少是合理的,如果不是更可取的话。像if (a(x) || b(x)) return true 这样的短路代码无法维护从a()b() 派生的err 值。

归结为风格。

如果简单代码由几行组成,任何方法都足够了。但考虑到a(x)b(x)c(x) 之间可能存在大量代码交织,建议不要使用样式 2,因为它分隔了多行代码的流程。我喜欢风格 3 over 1,因为它更简洁一些,但 1 和 3 之间的差异很小。

为了应对风格 #2 在一处收集错误返回的能力,请考虑使用更高级别的函数包装器,该包装器明确确保所有序言和尾声活动都发生。

最佳选择:遵循小组的风格指南,否则最适合您的维护,而不是编写。

int16_t foo (x_t x, y_t y, z_t z) {  // added matching return type
    int16_t err = 0;

    // OP style 1
    err = a(x));
    if (err) return err;
    err = b(y);
    if (err) return err;
    err = c(z); 
    return err;

    // OP style 2
    err = a(x);
    if (!err) err = b(y);
    if (!err) err = c(z);
    return err;

    // style 3
    // via enum or define, create an OK symbol
    if ((err = a(x)) != OK) return err;
    if ((err = b(x)) != OK) return err;
    if ((err = c(x)) != OK) return err;
    // 
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-10
    • 1970-01-01
    • 2016-10-03
    • 2018-10-27
    相关资源
    最近更新 更多