无符号算术运算中的回绕
Posted
技术标签:
【中文标题】无符号算术运算中的回绕【英文标题】:Wraparound in unsigned arithmetic operation 【发布时间】:2016-04-04 12:37:04 【问题描述】:分析以下代码时出现以下 MISRA 违规:
#define z (USHORT)14745
#define Convert(x) ((((USHORT)x*(unsigned short)1000) + ((z) / (USHORT)2)) /(z))
static const USHORT array [2] =
Convert(176), -> Line "1"
Convert(206) -> Line "2"
;
在“1”、“2”两行都检测到以下 MISRA 违规:
积分提升:unsigned short 提升为 unsigned int。 参考 - ISO:C90-6.2.1.1 字符和整数
常量:。 MISRA-C:2004 规则 12.11;参考 - ISO:C90-6.1.2.5 类型
此强制转换的结果被隐式转换为另一种类型。
我的问题是:为什么在这个操作中会有一个环绕?!
注意:当我使用调试器检查array
的值时:
array [2] =
12,
14
哪些是正确的值。
【问题讨论】:
因为176000
和206000
都超过了USHORT
可以容纳的值。
凌乱的“转换”行应该是什么?宏的一部分?据我所知,它不是有效的 C。
@Vane,但是最后的结果是正确的,为什么?!
@Lundin,检查最后的编辑
规则 12.11 是建议性的。假设您在转换中“幸运”,并且有规则可以防止您“不幸”..
【参考方案1】:
首先,176 * 1000 不适合 16 位无符号短整数。因此,通过使用 MISRA,您可以防止代码中出现严重错误,因为该算法是根据有符号的 int
类型计算的,并且它的结果隐含地显示为无符号短。如果你得到了预期的结果,那完全是巧合/运气。
请注意,还有另外两个未报告的建议性 MISRA 违规行为:
不允许使用类似函数的宏(规则 19.7) 您应该使用一组预定义的整数类型定义,例如stint.h
(规则 6.3)
这两个都是非常好的规则,不应该忽略。 (它也应该警告你使用不带 'u' 后缀的文字。)
解决方法是用类型安全函数替换混乱的宏,该函数不包含隐式提升(给定 32 位 int
):
uint16_t convert (uint16_t x)
const uint16_t z = 14745u;
uint32_t result32;
result32 = (uint32_t)x * 1000ul + (uint32_t)z / 2ul / (uint32_t)z
return (uint16_t)result32;
【讨论】:
"因为算法是在有符号整数上计算的" 你的意思是 UNsigned int 吗? @ArmiaWagdy 不,所有小整数类型,例如unsigned short
都会在表达式中使用时隐式提升为(有符号)int
。这是微妙而危险的,也是为什么 MISRA 充满了非常完善的规则来防止此类错误。 MISRA 的主要任务之一实际上似乎是教程序员了解隐式晋升的危险。有很多资深的 C 程序员不知道 C 中的各种隐式类型提升规则。我承认,在 2004 年阅读 MISRA 之前,我自己并不知道。【参考方案2】:
与积分促销有关:
小于 int 的整数类型在操作时被提升 对他们进行。如果原始类型的所有值都可以 表示为 int,较小类型的值转换为 一个整数;否则,将其转换为无符号整数。
这样,Convert(206) [((((unsigned short)206*(unsigned short)1000) + ((z) / (USHORT)2)) /(z))]
宏将按如下方式执行:
-
“206”将提升为带符号整数。
“1000”将提升为带符号整数。
将执行“206 * 1000”操作,结果也是signed int 类型 -> “206000”。
z, 2 将被提升为signed int。
将执行 z / 2 操作,结果将是有符号 int 类型 -> "14745 / 2" = "7372"
6."206000" + "7372" 会执行,结果是signed int -> 213372
“213372”将除以“14745”得到“14”
【讨论】:
从技术上讲,整数常量已经是int
类型。尽管您将其中一些投给了 USHORT,但这些将被提升回int
。这意味着这个 sn-p 中的所有演员都是完全多余的。以上是关于无符号算术运算中的回绕的主要内容,如果未能解决你的问题,请参考以下文章