MISRA C:2004,移位错误
Posted
技术标签:
【中文标题】MISRA C:2004,移位错误【英文标题】:MISRA C:2004, error with bit shifting 【发布时间】:2012-02-26 14:34:27 【问题描述】:我正在使用带有 MISRA C:2004 检查的 IAR Workbench 编译器。 片段是:
#define UNS_32 unsigned int
UNS_32 arg = 3U;
UNS_32 converted_arg = (UNS_32) arg;
/* Error line --> */ UNS_32 irq_source = (UNS_32)(1U << converted_arg);
MISRA 错误是: 错误[Pm136]:从基础 MISRA 类型“unsigned char”到“unsigned int”的非法显式转换(MISRA C 2004 规则 10.3)
我在上面的任何代码中都没有看到任何unsigned char
。
Why did Misra throw an error here? 的讨论讨论了乘法,它可能具有与左移不同的提升规则。
我的理解是编译器应该将表达式提升为更大的数据类型,而不是降级为更小的数据类型。
这里到底发生了什么?
如何使代码符合 MISRA C:2004?
编辑 1:
将错误行改为:
UNS_32 irq_source = (UNS_32)((UNS_32) 1U << converted_arg);
不会使错误消失。
【问题讨论】:
Misra 定义了它自己的概念“底层类型”,对于常量来说,它是它们可以适应的最小类型。这与表达式的语言类型不同。这意味着1U
具有MISRA 基础类型unsigned char
。话虽如此,我不明白为什么编译器应该在这里发出警告——我会在下周工作时研究它......
【参考方案1】:
在 MISRA 的早期,应用它的程序有时会针对行为不符合尚未发布的 C89 标准的编译器。在发明 C 的机器上,对 16 位值的操作与对 8 位值的操作成本相同。将char
值提升为int
,并在存储回char
时截断结果实际上比直接对char
值执行算术更便宜、更容易。虽然 C 标准一经发布,就会要求所有 C 实现必须将所有整数值提升为至少可以容纳范围 -32767..32767 或 unsigned
的 int
类型可以容纳至少 0..65535 的类型,或者一些更大的类型,1980 年代针对 8 位机器的编译器并不总是这样做。
虽然现在尝试使用无法满足这些要求的 C 编译器似乎很疯狂,但 1980 年代的程序员经常面临选择使用“C-ish”编译器还是用汇编语言编写所有内容。 MISRA 中的一些规则,包括“固有类型”规则,旨在确保程序即使在将int
视为 8 位类型的奇怪实现上运行也能正常工作。
【讨论】:
【参考方案2】:第二个问题:
如何使代码符合 MISRA C:2004?
您可以以符合 MISRA 的方式编写如下:
typedef unsigned int UNS_32;
UNS_32 test(void);
UNS_32 test(void)
UNS_32 original_arg = 3U;
UNS_32 irq_source = 1UL << original_arg;
return irq_source;
回到第一个问题:
这里到底发生了什么?
第一条规则 10.3 规定,复杂整数表达式不得转换为比基础类型更宽的类型。
理解错误消息的一个关键是概念底层类型,这是一个 MISRA-C 特定的概念。简而言之,常量的底层类型是它可以适应的最小类型。在这种情况下,1U
具有基础类型 unsigned char
,尽管它具有语言类型 unsigned int
。
10.3 规则背后的基本原理是避免在大于部分的上下文中使用操作结果的情况。这方面的标准示例是乘法,其中 alpha
和 beta
是 16 位类型:
uint32_t res = alpha * beta;
这里,如果int
是16位,则乘法将在16位中进行,然后将结果转换为32位。另一方面,如果int
为32 位或更大,则乘法将以更大的精度执行。具体来说,当乘以 0x4000 和 0x10 时,这会使结果不同。
MISRA 规则 10.3 解决了这个问题,强制转换结果被放置在一个临时文件中,该临时文件稍后被转换为更大的类型。这样一来,您就不得不以一种或另一种方式编写代码。
如果打算使用 16 位乘法:
uint16_t tmp = alpha * beta;
uint32_t res = tmp;
另一方面,如果意图是 32 位乘法:
UNS_32 res = (UNS_32)alpha * (UNS_32)beta;
因此,在这种情况下,表达式1U << count
是潜在的问题。如果 converted_arg
大于 16 位,则在使用 16 位 int
s 时可能会出现问题。但是,MISRA 确实允许您编写 1UL << count
或 (UNS_32)((UNS_32)1U << original_arg)
。您提到 MISRA 检查器在后一种情况下发出了错误——我的没有,所以请再次检查。
所以,在我看来,您使用的 MISRA C 检查器正确识别出违反了规则 10.3。
【讨论】:
是否有语法将常量与unsigned char
和unsigned int
区分开来。根据您的回复,1UL
表示未签名 long。整数有后缀吗?
不,很遗憾,没有这样的后缀。从语言的角度来看,1
是 int
,1U
是 unsigned int
。从 Misra-C:s 的角度来看,它们有 signed char
和 unsigned char
作为底层类型。我认为(int)1
会让你最接近,但如果有一个明确的大小会更好,比如(uint32_t)1
。
@Lindydancer:不仅更好,而且实际上是 MISRA 标准所要求的。【参考方案3】:
在 MISRA 规则指定的 C89 中,以U
为后缀的整数常量的类型是列表“unsigned int, unsigned long int”中的第一个,其中它的值可以被代表。这意味着1U
的类型必须是unsigned int
。
按位移位运算符的定义指定对每个操作数执行整数提升(这不会更改unsigned int
),并且结果的类型是提升的左操作数的类型。在这种情况下,(1U << converted_arg)
的结果类型因此是unsigned int
。
这里唯一的显式转换是将这个unsigned int
值转换为unsigned int
,所以这一定是编译器警告的内容——尽管看不到unsigned char
,这意味着检查器似乎是越野车。
但从技术上讲,从unsigned int
到unsigned int
的这种转换似乎违反了规则 10.3,该规则规定“复杂表达式”的结果只能转换为 narrower 类型 - 并且转换为 same 类型显然不是转换为更窄的类型。
演员表是不必要的 - 我会简单地省略它。
【讨论】:
请注意,Misra:s 的“基础类型”与语言类型不同,例如,根据 Misra 的说法,1U 具有基础类型unsigned char
。所以陪审团仍然不确定这是否是一个错误......
问题不是从“unsigned int”到“unsigned int”的转换问题,而是从底层类型(预期类型)和C中表达式的实际类型。这里底层类型是“unsigned char”因为字面值 1U 可以适合“char”,而表达式的类型是“unsigned int”。以上是关于MISRA C:2004,移位错误的主要内容,如果未能解决你的问题,请参考以下文章
MISRA C:2012 Dir-1.1(只记录常犯的错误和常用的规则)Bit-fields inlineC99,NOT support in C90 #pragma
数组移位/错误索引/i = [x+y*size+z*size*size]