繁体   English   中英

转换有符号的 int 与 short 时符号扩展的不一致

[英]Inconsistencies in sign extension when shifting signed int vs short

int main(){
  signed int a = 0b00000000001111111111111111111111; 
  signed int b = (a << 10) >> 10;
  // b is: 0b11111111111111111111111111111111

  signed short c = 0b0000000000111111; 
  signed short d = (c << 10) >> 10;
  // d is: 0b111111

  return 0;
}

假设int是 32 位,而short是 16 位,

为什么b会扩展符号而d不会扩展符号? 我已经在 x64 上用 gdb 测试了这个,用 gcc 编译。

为了扩展short符号,我不得不使用两个单独的变量,如下所示:

  signed short f = c << 10;
  signed short g = f >> 10;
  // g is: 0b1111111111111111

signed short的情况下,当在表达式中使用小于int的 integer 类型时,它(在大多数情况下)被提升为int类型。 这在C 标准的第 6.3.1.1p2 节中有详细说明:

可以在可以使用intunsigned int的表达式中使用以下内容

  • object 或具有 integer 类型(除intunsigned int之外)的表达式,其 integer 转换等级小于或等于intunsigned int等级。
  • _Boolintsigned intunsigned int类型的位域。

如果int可以表示原始类型的所有值(受宽度限制,对于位域),则该值将转换为int 否则,它将转换为unsigned int 这些被称为integer 促销活动 integer 促销活动不会改变所有其他类型

这种提升特别发生在第 6.5.7p3 节中指定的按位移位运算符的情况下:

integer 提升在每个操作数上执行。 结果的类型是提升的左操作数的类型。 如果右操作数的值为负数或大于或等于提升的左操作数的宽度,则行为未定义。

因此将short值 0x003f 提升为int值 0x0000003f 并应用左移。 这导致 0x0000fc00,右移得到 0x0000003f 的结果。

signed int案例更有趣一些。 在这种情况下,您将值 1 的位左移到符号位。 这会根据 6.5.7p4 触发未定义的行为

E1 << E2的结果是E1左移E2位位置; 空出的位用零填充。 如果E1具有无符号类型,则结果的值是E1×2 E2 ,比结果类型中可表示的最大值多模一减少。 如果E1有带符号类型和非负值,并且E1×2 E2在结果类型中是可表示的,那么这就是结果值; 否则,行为未定义。

因此,虽然 output 得到的signed int案例是您可能期望的,但它实际上是未定义的行为,因此您不能依赖该结果。

integer 促销活动根据 C 2018 6.5.7 3 自动将short转换为int

integer 提升在每个操作数上执行……

因此(c << 10)int 0b111111向左移动 10 位,产生(在您的 C 实现中)32 位int 0b00000000000000001111110000000000。 其中的符号位为零; 这是一个正数。

当您signed short f = c << 10; c << 10的结果太大而无法放入有signed short 它是 64,512,高于您的signed short可以代表的最大值 32,767。 在赋值中,值被转换为左操作数的类型。 根据 C 2018 6.3.1.3 3,转换是实现定义的。 GCC 将此转换定义为以 65,536 为模(类型中位数的两倍)。 所以转换 64,512 得到 64,512 − 65,536 = −1024。 所以f设置为 -1024。

然后,在f >> 10中,您正在移动一个负值。 作为有signed shortf仍被提升为int ,但这种转换保留了该值,导致int值为 -1024。 然后转移。 此移位是实现定义的,并且GCC 将其定义为使用符号扩展移位 所以-1024 >> 10的结果是 -1。

对于符合 C 标准的初学者(6.5.7 位移位运算符)

3 对每个操作数执行 integer 提升。 结果的类型是提升的左操作数的类型。

因此这个值

signed short c = 0b0000000000111111;

在此声明中使用的表达式中

signed short d = (c << 10) >> 10;

被提升为 integer 类型int 由于值为正,因此提升的值也是正的。

因此这个操作

c << 10

不接触符号位。

另一方面,这段代码片段

signed int a = 0b00000000001111111111111111111111; 
signed int b = (a << 10) >> 10;

具有未定义的行为,因为根据 C 标准的同一部分

4 E1 << E2的结果是E1左移E2位; 空出的位用零填充。 如果 E1 具有无符号类型,则结果的值为 E1 × 2E2,比结果类型中可表示的最大值多模一减少。 如果 E1 具有带符号类型和非负值,并且 E1 × 2E2 在结果类型中是可表示的,那么这就是结果值; 否则,行为未定义。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM