MISRA C 2012 规则 15.4 用于终止任何迭代语句的 break 或 goto 语句不应超过一个

MISRA C 2012 Rule 15.4 There should be no more than one break or goto statement used to terminate an any iteration statement

我正试图摆脱代码中的多个 break 和 goto 语句。 正如规则所建议的那样,我们不应在任何迭代语句中使用多个 break 或 goto 语句

示例代码:

 for(int32_t i = 0; i < 10; i++) { 
  if (i == number1) {                
      return_val = 2*number1 + number2; 
      break;
  }

  number1 += (5 * number2 + 2*number3);

  if (i == number2) {
      return_val = number1 + (3 * number3);
      break;                  
  }

  number1 += 2 * number3;

  if (i == number3) {
      return_val = number1 + (2 * number2);
  }
}

我试过使用嵌套的 if 语句,但这不是终止循环的解决方案。

而且如果有 goto 语句而不是 break,那么可以解决什么问题。

带有转到的示例代码:

for(int32_t i = 0; i < 10; i++) { 
  if (i == number1) {                
      return_val = 2*number1 + number2; 
      goto LABE1;
  }

  number1 += (5 * number2 + 2*number3);

  if (i == number2) {
      return_val = number1 + (3 * number3);
      goto LABE2;                  
  }

  number1 += 2 * number3;

  if (i == number3) {
      return_val = number1 + (2 * number2);
  }
}

这是一个相当晦涩的算法。实际上,这通常是许多 MISRA 规则的归结点,它们会阻止您编写奇怪的代码。虽然我不能确定,但​​感觉这段代码依赖于各种全局变量,可能有更好的方法来编写它。

但是,如果算法与您编写的算法完全一样并且没有办法绕过它,那么它就是这样。在那种情况下,我会用多个 return 语句重写代码,这违反了另一个(建议的)MISRA 规则。但是,最好偏离该规则,而不是更合理的反对多个 break/goto.

的规则
for(int32_t i = 0; i < 10; i++) { 
  if (i == number1) {                
      return 2*number1 + number2; 
  }

  number1 += (5 * number2 + 2*number3);

  if (i == number2) {
      return number1 + (3 * number3);
  }

  number1 += 2 * number3;

  if (i == number3) {
      return number1 + (2 * number2);
  }

这比问题中的任何备选方案都更​​具可读性。

(请注意,您还必须 u 为所有整数常量添加后缀以符合 MISRA 标准)

我认为15.4和15.5是两个极具争议的MISRA建议。请记住,它们不是必需的,而是建议性的,然后您可以将它们分类(如果您的组织批准)为 "disapplied" ()。在我看来,遵循这些规则会让你的代码更冗长、更难阅读,也更容易出错。

正如我所说,这只是我的意见,让我们尝试一种循序渐进的方法。我没有使用专有名称,但您必须从您的域中选择合适的名称。

标记并保留一个 break

您可以使用一个简单的 _Bool 标志。假设您包括 stdbool.h:

bool flag = false;

for(int32_t i = 0; i < 10; i++) { 
  if (i == number1) {                
      return_val = 2*number1 + number2; 
      flag = true;
  } else {
      number1 += (5 * number2 + 2*number3);

      if (i == number2) {
          return_val = number1 + (3 * number3);
          flag = true;
      } else {
          number1 += 2 * number3;

          if (i == number3) {
              return_val = number1 + (2 * number2);
          }
      }
    }

    if (flag)
        break;
}

请为 flag 取一个更好的名字,最好描述一下情况。如您所见,我们只有一个 break,但我们在易读性方面付出了巨大的代价。

标记并用 continue

替换 break

我们能做什么?将 break 替换为 continue 并在循环条件中使用标志:

bool flag = true;

for(int32_t i = 0; flag && i < 10; i++) { 
  if (i == number1) {                
      return_val = 2*number1 + number2;
      flag = false;
      continue;
  }

  number1 += (5 * number2 + 2*number3);

  if (i == number2) {
      return_val = number1 + (3 * number3);
      flag = false;
      continue;
  }

  number1 += 2 * number3;

  if (i == number3) {
      return_val = number1 + (2 * number2);
  }
}

稍微好一点,但事实是我们没有解决这段代码中的主要问题。

将其移至单独的函数

如果此代码片段是更大函数的一部分,那么事实是我们正在解决错误的问题。可能导致问题的不是 break 本身 而是它在更大范围内的使用。在 C 中我们没有 std::optional<T> 那么我们可以使用 out 参数(但这不是实现此结果的唯一技术):

for(int32_t i = 0; < 10; i++) {
    if (foo(i, &number1, number2, number3, &return_value)) {
        break;
    }
}

具有类似于我们使用 flag 的第一个实现的单独功能。

bool foo(int32_t i, int32_t* number1, int32_t* return_val) {
  bool has_result = false;

  if (i == *number1) {                
      *return_val = 2 * *number1 + number2; 
      has_result = true;
  } else {
      // ...
  }

  return has_result;
}

更好的是,如果你能忽略15.5,那么这个函数将非常容易编写和阅读:

if (i == *number1) {                
    *return_val = 2 * *number1 + number2; 
    return true;
}

// ...

不要忘记在适当的地方添加 const。我很确定这可以重构得更好(函数参数太多,混合值和指针,这两个函数之间的耦合太大)但是由于虚拟名称和周围代码的缺乏,很难提出更好的建议;我想强调的是将代码移到一个单独的函数中。

如果分支内的代码足够复杂,那么引入三个独立的函数甚至可能有更多好处。 2 * *number1 + number2 是什么?它在计算什么?

欢迎在 Code Review 上 post 您的完整代码(包括周围代码和真实姓名,解释其用途)。