应用位运算符"~"后将"int"转换为"unsigned short"

13

我使用的静态分析工具对这段代码提出了警告:

uint16 var1 = 1U;
uint16 var2 = ~var1;

我查看了MISRA C 2004的规则,发现了10.5条规定:

如果按位运算符~和<<应用于底层类型为unsigned charunsigned short的操作数,则结果应立即转换为操作数的底层类型。

好的,这不是问题,隐式转换会被应用(我认为“转换”指的是隐式或显式转换)。但是10.1条规则说:

整数类型的表达式的值,在表达式比较复杂时不得隐式转换为不同的底层类型。

之前的一个复杂操作的例子是:~u16a

我修改了我的代码:

uint16 var1 = 1U;
uint16 var2 = (uint16) ~var1;

我收到了另一个警告:我认为将负值的int转换为unsigned int值不安全。我查看了C99标准(ISO C99)§6.3.1.3,但我不明白int与unsigned short的转换是否已经明确定义。

EmbeddedGurus 文章中我读到:

c = (unsigned int) a; /* Since a is positive, this cast is safe */

我的问题:

  1. signed int 转换为 unsigned short 是否存在未定义行为?
  2. 如果存在,如何在安全的方式下使用补码运算符与 unsigned short?

这里有一些很好的答案解释了C语言中的移位运算符如何进行数值提升。 - chouaib
2个回答

18
算术和位运算符的操作数在计算值之前始终遵循标准提升。任何比int类型更短的东西都将被提升为int或unsigned int,具体取决于平台(即取决于int是否能表示正在提升的类型的所有值)。
在您的平台上,uint16_t被标准提升为int,因为您的int可以表示uint16_t的所有值。然后对该int值应用按位取反,这是问题的原因。
为了获得与平台无关的确定性结果,请自己将该值转换为unsigned int:
 uint16_t var2 = (uint16_t) ~((unsigned int) var1);

请注意,这始终是正确的,因为要求unsigned int能够表示uint16_t的所有值。


如果类型是 int32_t 而不是 int16_t,那么正确的编写代码的方式是什么?因为在某些编译器上,long 可能是 64 位,而 int 可能是 16 位。 - supercat
int16_t 可以始终提升为 int,如果需要提升,那么您不必编写任何内容。 (也就是说,在有符号整数上进行位运算通常是不明智的。) - Kerrek SB
因此,我的(预期的)问题是,如果类型是uint32_t而不是uint16_t,应该如何编写代码。将其转换为unsigned int会在unsigned int为16位的平台上失败,而将其转换为unsigned long可能会导致值截断,如果该类型为64位。在我看来,为了使C成为编写可移植代码的体面语言,它需要包括固定大小的无符号类型不会提升 - supercat
@supercat:请将其作为一个独立的问题提出来。 - Kerrek SB
@Kerrek SB:好的,我避免使用显式转换到扩展无符号整数类型来进行整数提升。但是使用“int”违反了MISRA规则6.3(使用指示大小和有符号性的typedef)。此外,我认为增加大小的精度不是一个好主意,因为这会使代码不独立于平台。但我的第一个问题是:从有符号整数到无符号整数的显式转换是否是一种不安全的转换?6.10.3说这是一种危险的转换,因为这意味着丢失符号...但这是我的目标,而显式转换就是这个意思,不是吗? - no_name
显示剩余3条评论

5
有符号整数向无符号短整型的显式转换会出现未指定行为吗?从C99草案的第6.3.1.3节"有符号和无符号整数"可以看出,由于模算术的缘故,有符号值向无符号值的转换是经过良好规定的:如果新类型是无符号类型,则通过重复加上或减去新类型中可以表示的最大值加一,直到该值在新类型的范围内。49) 因此,在您的情况下,负数将通过反复添加来进行转换:
UMAX + 1 

将负结果转换为无符号类型的范围内。

例如,将-1转换为无符号类型总是导致最大的无符号值,因为-1 + UMAX + 1始终为UMAX

  1. 如果可以,如何以安全的方式使用补码运算符和无符号短整数?

应用~运算符时,发生的情况是该值由于应用了操作数的整数提升而被提升为int,这在第6.5.3.3单目算术运算符中有所涵盖,其中说(我强调):

对操作数进行整数提升,并且结果具有晋升类型。 如果晋升类型是无符号类型,则表达式~E等效于该类型中可表示的最大值减去E

考虑到引用段落中的最后一句话,也许首先进行unsigned int强制转换可能会导致更直观的结果:

uint16 var2 = ~((unsigned int)var1);

由于需要应用显式转换,因此您最终会得到以下结果:

uint16 var2 = (uint16) ~((unsigned int)var1);

问题在于~1的值取决于int的表示方式(例如,使用补码表示时为-1,在二进制补码下为-2等),因此将其转换为unsigned short时结果也可能不同。 - T.C.
@T.C. 我删除了那个语句,因为你指出它太宽泛了。 - Shafik Yaghmour

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接