MISRA C:类型转换和<<, & 按位运算符错误

问题描述

在执行

上下文类型的定义

typedef uint32_t uint32;
typedef volatile uint32* canreg_txrx_pointer;
canreg_txrx_pointer canreg_txrx;
uint8_t valueRead,value;
#define CAN_DIR_BIT_POS ((uint8_t)2U)
#define CAN_OUT_BIT_POS ((uint8_t)1U)
#define CAN_IN_BIT_POS ((uint8_t)0U)

尝试对寄存器执行一系列 #1397-D (MISRA-C:2004 10.5/R) If the bitwise operators ~ and << are applied to an operand of underlying type unsigned char or unsigned short,the result shall be immediately cast to the underlying type of the operand

下面给出了一个没有通过 misra 检查的例子,然后给出了一个通过的例子

*canreg_txrx = (uint32)((*canreg_txrx & (~(uint32)(1U << CAN_OUT_BIT_POS))) | (uint32)(value << CAN_OUT_BIT_POS)); //doesn't pass MISRA
valueRead = (uint8_t)(((uint8_t)*regToOperateOn & (uint8_t)(1U << CAN_OUT_BIT_POS)) >> CAN_OUT_BIT_POS); //passes MISRA

我已尝试对违规行进行多次更改,但都没有导致 MISRA 允许它通过(除了随机,它只是没有给我警告)到我决定全部投出的地步,但仍然没有工作

*canreg_txrx = (uint32)((*canreg_txrx & (uint32)(~(uint32)((uint32)1U << (uint32)CAN_OUT_BIT_POS))) | (uint32)((uint32)value << (uint32)CAN_OUT_BIT_POS)); //doesn't pass MISRA

我真的很困惑如何修复此代码,以便它通过 MISRA,因为此按位逻辑在多个地方使用并且所有这些都没有通过。我认为如果一切都被强制转换为相同的类型,那么应该没有问题,因为它们都以相同的类型运行,但由于某种原因不起作用。

解决方法

我从未使用过 Misra,但我发现您的代码很难阅读,因为有 typedef、强制转换和括号的所有干扰。我的建议是将问题简化并将其分解为更小的部分:

*canreg_txrx = (uint32)((*canreg_txrx & (~(uint32)(1U << CAN_OUT_BIT_POS))) | (uint32)(value << CAN_OUT_BIT_POS)); // =>

*canreg_txrx &= ~(1U << CAN_OUT_BIT_POS);
*canreg_txrx |= value << CAN_OUT_BIT_POS; // maybe =>

#define BIT_CLEAR(val,pos) ((val) &= ~(1U << (pos)))
BIT_CLEAR(*canreg_txrx,CAN_OUT_BIT_POS);
*canreg_txrx |= value << CAN_OUT_BIT_POS; // *

'*' 如果值有点,那么这只是位设置,否则想不出好名字。

然后通过 Misra 运行它,解决它抱怨的问题。

,

MISRA-C 有点困惑,因为它坚持在转换完成之后转换为“基础类型”(MISRA-C:2004 术语)。我更喜欢在转换之前对一个大的无符号类型进行强制转换,因为这排除了所有可能发生的问题。

这些规则努力防止的是通过 Implicit type promotion rules 意外转换为有符号类型,然后是位移,这可能很危险并导致各种错误,包括符号更改、意外符号扩展、未定义左移有符号值时的行为,右移时的 impl.defined 行为,等等。

在您的情况下,您会遇到这些问题,因为您没有将 ~ 的结果转换为此处的基础类型:~(uint32)(1U << CAN_OUT_BIT_POS))。您的静态分析器似乎给出了误报,因为此处的基础类型似乎是 uint32_t。没有更小的类型需要考虑,除非因为您似乎有 32 位整数(因为您有 32 位 CAN 寄存器),但该工具可能不知道您的整数是 32 位,在这种情况下 1U 可以已经是 16 位值。

否则,也许工具会变得混乱,因为您在定义这些宏时使用了混淆的强制转换……但是对于移位运算符而言,正确的操作数的类型并不重要(尽管它也被提升了)。

在这种特殊情况下,这一切都不应该是危险的,因为您期望的是未签名的 32。警告、强制转换以及所有内容 - 只是噪音,使您的代码变得脆弱且不可读。

我建议您通过以下方式挽救代码:

  • 摆脱宏中的所有 uint8 强制转换,这只是噪音,可能会导致问题。
  • 摆脱自制的 uint32 等类型并使用标准 C uint32_ttypedef uint32_t uint32; 是不应该存在于任何 C 程序中的烂代码。使用现有的行业标准,不要发明新的标准。
  • 不要将指针隐藏在 typedef 后面,这是非常危险的做法,会使代码难以阅读。
  • 摆脱多余的演员阵容。假设这是一个 32 苦味,您不应该将 1U 类型转换为 uint32_t,因为它们已经是相同的类型。
  • 将不可读的行分解成几个表达式。例如我们可以做const uint32_t mask = ~(1U << CAN_OUT_BIT_POS);。由于基础类型已经是 uint32_t,因此不需要强制转换。您的 MISRA 检查器可能太笨了,无法意识到您有 32 位整数,因此您可能必须添加强制转换 (uint32_t)~ ...。不是为了符合 MISRA-C:2004,而是为了让一个潜在的愚蠢工具静音。
  • 如果您只需要强制转换使特定工具静音,您应该对此做出明确的评论:/* this cast just to silence BrokenAnalyser from Bugworks */。表明您知道自己在做什么,并将误报与实际必要的转换分开。
  • MISRA-C 的另一个有效问题是,您不应在同一表达式中多次访问 volatile 寄存器,因为这会引入未排序的副作用,而且您无法真正判断何时何地访问实际寄存器访问发生的那一行,加上它阻止编译器优化。这是您尚未发现的另一个 MISRA 违规行为。这也可以通过将长表达式分解成几个来解决。

一个例子:

const uint32_t mask = ~(1U << CAN_OUT_BIT_POS);
uint32_t txrx = *canreg_txrx; // volatile access on a line of its own
txrx = (txrx & mask) | ((uint32_t)value << CAN_OUT_BIT_POS);
*canreg_txrx = txrx;

中间的 txrx 行将得到优化,并且在机器代码中你只会得到一些指令。据我所知,这应该符合 MISRA-C:2004 和 2012 标准。这也不会移动隐式提升的 value 然后在之后进行转换。