带符号long long大数右移操作异常原因咨询
当对字节执行移位操作时,存在一些不符合预期的场景。例如执行以下代码:
printf("shifted bytes %llx\n", (((long long)1 << 63 ))>>1);
其输出为c000000000000000,但使用unsigned long long类型或初始左移位数小于63时不会出现该问题。由于1在带符号与无符号long long中均存储为0000000000000001,且仅执行位运算,理论上不应存在差异,请问该现象为何会发生?
这是因为带符号整数的右移是算术右移,而无符号整数的右移是逻辑右移,二者行为完全不同,核心差异在于符号位的处理:
第一步:
(long long)1 << 63
对于64位带符号long long,最高位是符号位(0表示正数,1表示负数)。将值为1(二进制仅最低位为1,其余全0)的带符号数左移63位后,符号位被置为1,得到long long类型的最小值:0x8000000000000000(二进制最高位为1,其余位全0)。
注:虽然C标准对带符号数左移溢出的行为定义为未定义,但主流编译器都会按算术移位规则处理,直接移动位并保留符号位的变化。第二步:将上述结果右移1位
带符号整数的算术右移会用符号位(此时为1)填充左侧空出的位,而非用0填充。因此0x8000000000000000右移1位后,最高两位变为11,其余位为0,最终结果就是0xc000000000000000,与你看到的输出一致。无符号类型无异常的原因
使用unsigned long long时,右移是逻辑右移,无论何种情况都用0填充左侧空位。(unsigned long long)1 << 63得到0x8000000000000000,再右移1位会变成0x4000000000000000,符合"单纯移位"的预期结果。左移位数小于63时无异常的原因
当左移位数小于63时,带符号数的符号位不会被置1,移位后仍为正数。此时算术右移和逻辑右移的效果一致(符号位为0,填充的也是0),所以结果与无符号类型的移位结果相同。
内容的提问来源于stack exchange,提问作者user20695956

