繁体   English   中英

MISRA C 2012 规则 15.4 并用 break 替换 goto

[英]MISRA C 2012 Rule 15.4 and replacing goto's with break's

关于 MISRA C 2012 规则 15.4 -“用于终止任何迭代语句的 break 或 goto 语句不应超过一个。” - 这个例子正确吗? 任何人都可以用一些工具(MISRA 检查器)确认这一点吗?

do {
    retval = do_smth();
    if (retval != OK) {
        break;
    }

    retval = do_smth2();
    if (retval != OK) {
        break;
    }

    retval = do_smth3();
} while (0u);

这只是一个概念,但我在这里尝试的是用级联的break替换goto的级联(不幸的是在这种情况下被禁止)。 我的观点是do { } while(0u); 不是迭代语句。
你认为呢?

首先,您的代码确实不遵循规则 15.4,因为您在迭代语句1)中有 3 个break 但这只是一个建议,只要代码可读且易于理解,像您一样使用多个中断并没有错。

这些 MISRA 规则的主要理由是防止“复合语句意大利面条”,其中复杂代码从多个嵌套复合语句中中断。 在盲目遵循这些规则之前,了解其基本原理很重要。 所以在这种情况下,只需考虑保留代码原样 - 咨询规则不需要偏差。

否则,有几个选项,如下所示:


MISRA-C 的一个问题是它不允许从 function 多次返回,即使它使代码更具可读性。 否则,显而易见且最易读的解决方案是使用 function:

type do_stuff (void);
{
  type retval;

  retval = do_smth();
  if (retval != OK) { return retval; }

  retval = do_smth2();
  if (retval != OK) { return retval; }

  retval = do_smth3();

  return retval;
}

我通常的解决方案是使 MISRA-C 永久偏离多重返回规则,并在它使代码更具可读性的情况下允许它,就像在这种情况下一样。

否则,第二个最佳选择可能是旧的“on error goto” - MISRA-C:2012 中放宽了禁止goto的规则,因此它现在只是建议性的。

  retval = do_smth();
  if (retval != OK) { goto error; }

  retval = do_smth2();
  if (retval != OK) { goto error; }

  retval = do_smth3();
  if (retval != OK) { goto error; }

  goto everything_ok;

  error:
    /* error handling */

  everything_ok:

如果上述 forms 都没有问题,因为您对 MISRA-C 非常严格,那么第三个选项可能是这样的,我相信它 100% 符合 MISRA-C:

typedef type do_stuff_t (void);

do_stuff_t* const do_stuff[N] = { do_smth, do_smth2, do_smth3 };
type retval = OK;

for(uint32_t i=0u; (i<N) && (retval==OK); i++)
{
  retval = do_stuff[i]();
}

我的观点是 do { } while(0u); 不是迭代语句。

C 语言不同意你的观点。

1)从 C17 开始:

6.8.5 迭代语句

句法

迭代语句:
while (表达式)语句
do语句while (表达式);

我会用这个替换你的代码:

  retval = do_smth();
  if (retval == OK) {
    retval = do_smth2();
  } 
  if (retval == OK) {
    retval = do_smth3();
  }
  • 没有假的
  • 没有伪装成 break 的 goto
  • 因此甚至没有一个 goto/break
  • 因此不再有 MISRA 问题
  • 奖励:行数是原始代码的一半

顺便说一句:最后一次休息( break; // <- 3rd )无论如何都没用

用于终止任何迭代语句的 break 或 goto 语句不应超过一个。

您的示例在一个 do-while (迭代语句)中有 3 个中断,所以我认为这是不正确的。 break是控制流/循环控制语句,仅在循环上下文中有效。 我不认为你的论点在这里是有效的,虽然我知道你要去哪里。

TL;DR: do-while仍然是一个迭代语句,即使它只运行一次。

do {
    retval = do_smth();
    if (retval != OK) {
        break; // <- 1st
    }

    retval = do_smth2();
    if (retval != OK) {
        break;  // <- 2nd
    }
    retval = do_smth3();
    if (retval != OK) {
        break; // <- 3rd
    }
} while (0u);

我不知道这段代码是否足够好。 它虽然有效。

switch(0) {
default:
    retval = do_smth();
    if (retval != OK) {
        break;
    }

    retval = do_smth2();
    if (retval != OK) {
        break;
    }

    retval = do_smth3();
    break;
}

我不是 MISRA 方面的专家,所以这可能由于其他原因被禁止,但在这种情况下,我可能会想使用这样的东西:

if((retval = do_smth()) != OK)
    ;
else if((retval = do_smth2()) != OK)
    ;
else if((retval = do_smth3()) != OK)
    ;

没有goto ,没有break ,没有迭代,没有重复的测试。

空语句看起来很奇怪,这是真的,尽管在实践中,经常会有一些错误记录代码放在那里。 如果裸分号不符合您的口味,您可以使用{ }{ /* do nothing */ }

条件中的赋值(如经典的while((c = getchar()) != EOF)循环)可能会让新手感到困惑(我怀疑 MISRA 可能或多或少地禁止它们),但在许多情况下,如果你能容忍它们,它们真的可以帮助消除其他各种丑陋。

在过去的项目中,我介绍了整个项目中使用的标准宏 ER_CHK

为清楚起见进行了简化,定义为:

#define ER_CHK(st) if (st != OK) {goto  EXIT;} else ;

您可以按如下方式使用它:

status_t foo() {
   status_t  st;
   st = bar1(); ER_CHK(st);
   st = bar2(); ER_CHK(st);
   st = bar3(); ER_CHK(st);

   EXIT : ; // ER_CHK calls get here in case of non OK status
   if (st != OK) {
      // put exception handling code here
   }
}

这会强制执行单一的标准退出——让真实代码非常干净。

我实际上将宏定义为包含发现故障的日志:例如

#define ER_CHK(st) if (st != OK) {log(st,__FILE__,__LINE__); goto  EXIT;} else ;

这在堆栈展开时提供了一个日志“回溯”。

要点是在整个项目中都使用它。 它允许普通代码脱颖而出,并且有一种处理异常的标准方法,可以将其视为习语、模式或语言扩展。

它使普通(非异常)代码更具可读性,因为

  • 行数较少
  • 只有相关代码可见
  • 无需区分正常的“ifs”和异常处理 ifs。

For a fuller description see https://forum.misra.org.uk/archive/index.php?thread-368.html and a full article "Exception Handling in Embedded C Programs", C/C++ Users Journal,

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM