【问题标题】:MISRA C 2012 Rule 15.4 and replacing goto's with break'sMISRA C 2012 规则 15.4 并用 break 替换 goto
【发布时间】:2021-02-25 13:32:33
【问题描述】:

关于 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); 不是迭代语句。
你怎么看?

【问题讨论】:

  • do-while 循环仍然是一个迭代语句,无论条件是否确保只有一次迭代。
  • 上下文是什么?这是在宏声明中还是在什么地方,或者你为什么要使用 do-while(0)?
  • @Lundin:我认为早期的代码使用了几个gotos(到同一个标签),do/while 在这里用于实现与breaks 相同的行为。
  • 正如@MOehm 所说。上下文在代码示例下方的问题中进行了描述。
  • 除了一次性循环是否符合“迭代”条件之外,相关代码的可读性更少更多令人困惑它取代了什么,因此不能证明它是本应确保可读性、可靠性和可维护性的练习的一部分。

标签: c misra


【解决方案1】:

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

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

否则,有如下几个选项:


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

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” - 禁止 goto 的规则在 MISRA-C:2012 中被放宽,因此现在只是建议性的。

  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:

如果由于您对 MISRA-C 非常严格,上述两种形式都不合适,那么第 3 个选项可能是这样的,我认为它 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 ( 表达式 ) ;

【讨论】:

  • 对“在盲目遵循这些规则之前了解基本原理”,“一个问题是它不允许多次返回,即使它使代码更具可读性”和“使永久偏差,并在它使代码可读”点的情况下允许它。当过于严格地遵守过于严格的规则直接导致代码的可读性、可靠性或可维护性降低时,它只会让我丧命。
  • 我接受这个答案,因为您参考了 C 标准来证明您的观点。我个人会选择第二个选项,因为我发现有一个返回点可以提高可读性并且在调试过程中很有帮助......但这是另一回事了。
  • @SteveSummit “在盲目遵循这些规则之前先了解其基本原理” 据我所知,MISRA-C 委员会也非常同意这一点(或者至少每次我与一个成员交谈时都有个别成员这样做) - 他们将文档命名为 guidelines 是有原因的。但是,我编写了函数指针版本,因为它可能还有其他一些好处,即集中维护。这段代码实际上是最简单形式的有限状态机。
  • @radoslav006 我认为这三个都很好,尽管第一个和第二个(以及您的原件)违反了各种 advisory 规则。但这是您需要格外挑剔的必需规则。
  • “在盲目遵循这些规则之前,请先了解其基本原理”。正确的。如果需要,请使用偏差(我们定义该过程是有原因的!)[免责声明...查看从属关系​​的个人资料!]
【解决方案2】:

我不知道这段代码是否足够好。但它确实有效。

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

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

    retval = do_smth3();
    break;
}

【讨论】:

  • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
【解决方案3】:

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

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

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

【讨论】:

  • 虽然在这个特定的示例中这看起来很简洁,但在进行大量错误检查的情况下,这将变得繁重且不必要的缓慢。请记住,如果发生故障,程序仍然必须执行一堆 if (retval == OK) 分支,这并不理想。
  • 我知道我可以通过这种方式缩短它 - 我已经这样做了,但这里有三件事:1. 你的代码不是 1:1 等效的,因为你需要在整个级联中进行所有检查,在breakgoto的情况下,您可以立即返回。 2. 这个例子很简单,但有时它会变得更复杂。 3. 无论如何,这不能回答我的问题。
【解决方案4】:

用于终止任何迭代语句的 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);

【讨论】:

  • 我会反对这一点——如果某些东西总是运行一次,那么根据定义就没有迭代。但这里更重要的是 MISRA 合规工具的想法,我对此非常好奇。
  • @radoslav006 它需要静态分析来确定 do-while 循环的次数——因为根据定义它是一个循环构造——我希望你不会反对。在一般情况下,您无法静态确定循环边界。但是,您通常可以保守地这样做。
  • @radoslav006 迭代语句是标准 C 中的正式术语。whiledo...while 是迭代语句。无论迭代次数如何。我在我的答案中添加了标准的引用。
  • @radoslav006 我会反对这一点——如果某些东西总是运行一次,那么根据定义就没有迭代。这种态度是有问题的——你正在寻找方法来解决问题您编写代码的报酬标准。
  • @radoslav006 哇。你仍然试图颠覆 MISRA:“1. 我从来没有建议 do { ... } while() 不是迭代语句。我建议 do { ... } while(0u) 是不是迭代语句 - 有区别。”不,没有区别 - do {...} while() is 是一个迭代语句。你仍然在玩,“如果我扭曲了这个意思,我就不必按照我得到的编码标准来编码。”这就是颠覆标准。您显然还需要阅读Law of Holes
【解决方案5】:

我不是 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 可能出于或多或少的原因禁止它们),但在许多情况下,如果你能容忍它们,它们真的可以帮助消除其他各种丑陋。

【讨论】:

  • MISRA-C 不允许在选择语句中进行赋值,因为另一个建议规则 13.4“不应使用赋值运算符的结果”。理由是“可能会严重损害代码的可读性”,而且“它会在语句中引入额外的副作用,从而更难避免未定义的行为”。考虑例如 retval = do_smth(retval) 这有点微妙的 UB。
  • 此外,MISRA-C 要求 else-if 链以最终的 else 结尾,在这种情况下,它只是 else ;。这是一个必需的规则,因此被认为更重要。在这种情况下,基本原理是防御性编程。
  • 但是,此代码应该 100% 符合 MISRA-C:if(do_smth(&amp;retval) != OK) ; else if(do_smth2(&amp;retval) != OK) ; else if(do_smth3(&amp;retval) != OK) ; else ; 因此,如果可以选择更改功能,那可能是最好的选择。随时将其添加到您的答案中:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-17
相关资源
最近更新 更多