我有以下代码:
unsigned char chr = 234; // 1110 1010
unsigned long result = 0;
result = chr << 24;
现在结果将等于18446744073340452864,二进制为1111 1111 1111 1111 1111 1111 1111 1111 1110 1010 0000 0000 0000 0000 0000 0000
。
当chr未签名时,为什么要进行符号扩展?
此外,如果我将班次从24更改为8,则结果为59904,即二进制的0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1110 1010 0000 0000
。为什么这里没有扩展? (任何23或更少的班次都没有对其进行符号扩展)
同样在我当前的平台上sizeof(long)
是8。
移位时自动转换为较大尺寸类型的规则是什么?在我看来,如果移位是23或小于chr被转换为无符号类型,如果它是24或更多,它会被转换为有符号类型? (为什么符号扩展甚至完成左移)
答案 0 :(得分:3)
使用chr = 234
,表达式chr << 24
将被隔离评估:chr
被提升为(32位有符号)int
并向左移位24位,产生一个负值int
。分配给64位unsigned long
时,符号位将传播通过64位值的最高32位。请注意,计算chr << 24
的方法本身并不受赋值的影响。
当移位仅为8位时,结果为正(32位带符号)整数,并且该符号位(0)通过unsigned long
的最重要的32位传播。
答案 1 :(得分:3)
要理解这一点,最容易根据值进行思考。
每个整数类型都有一个固定范围的可表示值。例如,unsigned char
的范围通常为0
到255
;其他范围是可能的,您可以通过选中UCHAR_MAX
中的limits.h
来找到您的编译器选项。
在整数类型之间进行转换时;如果值在目标类型中可表示,则转换结果为该值。 (这可以是不同的位模式,例如符号扩展)。
如果目标类型中的值无法表示,则:
现代系统通过左截断过多的位来处理带符号的超出范围的赋值;如果它仍然超出范围,则它保留相同的位模式,但值会更改为位模式在目标类型中表示的任何值。
转到你的实际例子。
在C中,有一种称为整体促销的东西。对于<<
,这发生在左侧操作数上;算术运算符会发生在所有操作数上。积分促销的效果是,任何小于int
的类型的值都会转换为类型为int
的相同值。
此外,<< 24
的定义乘以2 ^ 24(其中它具有提升的左操作数的类型),如果溢出则具有未定义的行为。 (非正式地:移入符号位会导致UB)。
因此,明确地进行所有转换,您的代码是
result = (unsigned long) ( ((int)chr) * 16777216 )
现在,此计算的结果为3925868544
,如果您使用的是32位int
的典型系统,则大于INT_MAX
,即2147483647,因此行为未定义。
如果我们想在典型系统上探索这种未定义行为的结果:可能发生的事情与我之前概述的超出范围分配的过程相同。 3925868544
的位模式当然是1110 1010 0000 0000 0000 0000 0000 0000
。使用2的补码将此作为int
的模式处理,得到int -369098752
。
最后,我们将此值转换为unsigned long
。 -369098752
超出了unsigned long
的范围;并且无符号目的地的规则是调整模ULONG_MAX+1
的值。所以你看到的价值是18446744073709551615 + 1 - 369098752
。
如果您的目的是以unsigned long
精度进行计算,则需要创建一个操作数unsigned long
;例如做((unsigned long)chr) << 24
。 (注意:24ul
无法正常工作,<<
或>>
的右侧操作数类型不会影响左侧操作数。