![](/img/trans.png)
[英]Rotate left and back to the right for sign extension with (signed short) cast in C
[英]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 节中有详细说明:
可以在可以使用
int
或unsigned int
的表达式中使用以下内容
- object 或具有 integer 类型(除
int
或unsigned int
之外)的表达式,其 integer 转换等级小于或等于int
的unsigned int
等级。_Bool
、int
、signed int
或unsigned 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 short
, f
仍被提升为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.