在调试一些嵌入式代码时,我遇到了这样的事情:
buffPtr = &a[5];
buffEndPtr = &a[10];
while (buffPtr != buffEndPtr)
{
*buffPtr = 0xFF;
buffPtr = &buffPtr[1]; /* MISRA improvement for: buffPtr++ */
}
为什么这个构造会比 (*buffPtr)++ 有所改进?
有一条 MISRA 规则规定唯一允许的指针数学是索引操作。
您展示的模式是一个执行不力的解决方法。这是丑陋/奇怪/不常见的,可能是基于对该规则目的的误解。它也可能违反另一条规则。
编写此代码的更好方法是:
for(i=5; i < 10; i++)
{
a[i] = 0xff;
}
2015-05-20 更新 - 由于这是公认的答案,因此违反了实际规则,由 embedded.kyle 提供:
MISRA-C:2004,规则 17.4(必需)或 MISRA-C:2012,规则 18.4(必需)数组索引应是唯一允许的指针算术形式。
违反的规则(*buffPtr)++
是:
MISRA-C:2004,规则 17.4(必需)或 MISRA-C:2012,规则 18.4(必需)
数组索引应该是唯一允许的指针算术形式。
他们在这条规则背后的推理:
使用数组下标语法的数组索引
ptr[expr]
是指针运算的首选形式,因为它通常比指针操作更清晰,因此更不容易出错。任何显式计算的指针值都有可能访问意外或无效的内存地址。数组索引也可能出现这种行为,但下标语法可以简化手动检查的任务。C 中的指针运算可能会让新手感到困惑 表达式
ptr+1
可能会被错误地解释为1
对ptr
. 事实上,新的内存地址取决于指针目标的字节大小。sizeof
如果应用不正确,这种误解可能会导致意外行为。
MISRA 的许多规则都有类似的理由。基本上,他们的思考过程是,如果您尽可能简单和明确地编写代码,代码将更具可读性和可维护性,因此会产生本质上更安全的代码。更安全的代码是 MISRA 标准背后的目的。
正如 Brian 指出的那样,有一些方法可以编写符合 MISRA 但仍然违反规则背后的意图的代码。在我看来, Brian 的for
循环示例将是最常见且易于理解的构造。
在 MISRA-C:2004 规则 17.4 中有一条禁止所有形式的指针算术的咨询规则。意图是好的,该规则的目的是试图禁止潜在危险的代码,例如:
stuff* p;
p = p + 5; // 5 stuff, not 5 bytes, bug or intentional?
和难以阅读的代码,例如
*(p + 5) = something; // harder to read but equivalent to p[5]
通常,目的是推荐在循环指向数据时使用整数迭代器而不是指针算法。
但是,该规则还禁止了各种可能不危险的基本指针操作,例如ptr++
. 一般来说,规则太严格了。
在 MISRA-C:2012 中,这条规则 (18.4) 被放宽,只禁止+ - += -=
运营商。
就您而言,这buffPtr = &buffPtr[1];
是规避规则 17.4 的错误尝试,因为该规则没有多大意义。相反,程序员决定混淆他们的程序,使其可读性降低,因此安全性降低。
处理此问题的正确方法是使用 ++ 运算符并忽略规则 17.4。这是一条建议性规则,因此不需要做任何偏差(除非本地 MISRA-C 实施出于某种原因另有说明)。如果您确实需要偏离,您可以简单地说该规则对 ++ 运算符没有任何意义,然后参考 MISRA-C:2012 18.4。
(当然,将整个循环重写为另一个答案所示的for循环是最好的解决方案)
不使用常识进行编程总是非常危险的,就像盲目地遵循 MISRA 而不了解规则背后的合理原理,或者在这种情况下缺乏此类原理。