C学习:移位幻影之左移一个负数,会发生什么?
问题背景
C语言中,移位是个简单的问题,但又是个高风险的问题。
简单在于,大部分场景都可以理解为乘或除2的幂。风险在于,C语言中对有符号数的移位与无符号数的移位异常场景,未定义:
- 不同位宽转换时的移位过程如何处理?
- 有符号数的左右移动,是逻辑移位,还是带符号移位?
- 无符号数的移动中,移动数为负或超过了符号位宽如何处理?
前两问题中,在《C学习:无符号有符号数的移位问题分析总结》和《C学习:隐藏在负数移位过程中的bug》有过一些探讨,这里就第最后一个问题做一些小结。
无符号数移位问题
之前在《C学习:一个百思不得其解的无符号数移位问题》,也初步聊过,但无法解释下面这个问题。
1<<-1
,对无符号数如1左移-1位会发生什么?
照例,先问下AI大模型看它如何应对。Prompt提问是:C语言中,执行这段语句:int b = 1 << -1, 会发生什么?
Copilot回答如下:
在C语言中,执行语句
int b = 1 << -1;
会导致未定义行为。原因如下:
- 位移操作符 :
<<
是左移操作符,用于将一个整数的二进制位左移指定的位数。- 负数位移:标准C语言不允许负数位移。位移操作的位数必须是非负整数。
因此,
1 << -1
是非法的,会导致未定义行为。这意味着程序的行为是不可预测的,可能会导致程序崩溃或产生错误的结果。
我理解,不可预测指的是该操作的结果有多义性,具体行为取决于编译器处理,比如可能结果:
- 为0,编译器翻译为右移1位,1>>1
- 直接报错返回,编译器直接返回运行错误码
我的环境,跑的结果是0,会告警,但不会报错。错的是将其作为除数,导致浮点除零。但编译器也没中断,而是将结果存为一个极小值,继续运行。
有符号数移位操作使低位置零问题
t = (t << 8) >> 8
,此代码中,t为有符号数int类型,移位结果是否固定?
显然,前文说过,有符号数移位是未定义行为,可能是算术,也可能是逻辑移位。但多数编译器,是使用的算术移位,即带符号移位。
所以,一般场景而言,该行代码能实现有符号数的低8位比特置零的效果。
但是,假如我们不允许对有符号数进行移位,该如何实现有符号数低8位置零操作呢?
容易想到的是,C语言中可通过除法这样实现:t = t / 256 * 256
扩展思考的是,可否不用除法,将有符号数移位过程等价转换到无符号数的位操作上去,从而更高效的实现?
c
/* Write C code in this online editor and run it. */
printf("Hello, World! \n");
int num = -10;
unsigned int numVer = num;
printf("0x%x\n", numVer);
num = -33554432; // -2^25()
printf("0x%x\n", num);
int num_shift = (num << 8) >> 8;
printf("0x%x\n", num_shift);
仍存疑中。