C中的“(i<<3)>>3”的问题及解析
偶然在写位输出函数时发现了“(i<<3)>>3”这个问题,下面我贴一段代码使问题更加清晰:
1 unsigned char i = -1;
2 i = (i<<3)>>3;
3
4 printf("%u\n",i);
按照正常思维,结果应该是31。因为8个1,向左移3位再移回来,左边3位应该补0。注意这里用的是unsigned char,向右移位才是逻辑移位。但程序运行时,输出的结果却是255。跟没有移位一样!
难道是编译器智商太高了,认为左移后又移回来这种傻事只有傻子才会做,于是给精简掉了?当下用的是VS2008自带的编译器,还用的是Debug模式,于是换了INTEL和MINGW最新版编译器重新编译,发现结果都一样。还是255!
这种感觉就跟突然间发现1+1不等于2一样,真是让人郁闷。郁闷归郁闷,问题还是得解决,(略去N次GOOGLE……)换了几个数据类型试了试,发现unsigned int就没事!unsigned int移位后,结果确实是左边3个比特位由1变成0了。问题越发诡异,还是看看编译器把这句移位编译成啥东西了,如下:
1 ; 10 : i = (i<<3)>>3;
2
3 00022 0f b6 45 fb movzx eax, BYTE PTR _i$[ebp]
4 00026 c1 e0 03 shl eax, 3
5 00029 c1 f8 03 sar eax, 3
6 0002c 88 45 fb mov BYTE PTR _i$[ebp], al
本段汇编的movzx使用是在意料之中的,movzx的填充方式是:恒用0来填充目的操作数的高位数据位。unsigned char移入32位CPU数据寄存器EAX的确应该首位全补0(原值有符号用movsx,原值无符号用movzx)。但,发现问题没有?右移居然使用的是sar汇编指令,算术右移sar把目的操作数的高位向低位移,空出的高位用最高位(符号位)填补。前面说过了,unsigned char应是逻辑右移。问题先放这,来看看数据类型改为unsigned int后生成的汇编:
1 ; 10 : i = (i<<3)>>3;
2
3 00025 8b 45 f8 mov eax, DWORD PTR _i$[ebp]
4 00028 c1 e0 03 shl eax, 3
5 0002b c1 e8 03 shr eax, 3
6 0002e 89 45 f8 mov DWORD PTR _i$[ebp], eax
本段汇编使用mov,因为当前的数据类型unsigned int,直接塞入寄存器即可。但sar指令换成了shr指令,这次使用的是逻辑移位了。
是什么导致这种情况发生呢?下面来看看C权威书籍《C程序设计语言 - 第二版》中咋说的。(中文版,P173 A.6.1)整形提升:如果原始类型的所有值都可用int类型表示,则其值将被转换为int类型;否则将被转换为unsigned int。将要进行位操作的变量为unsigned char时,编译器把unsigned char类型提升为int了,于是由无符号类型变成有符号类型,众所周知,有符号类型右移操作为算术右移,于是汇编代码中便生成了sar算术右移指令。
这样“(i<<3)>>3”问题就解决了,
- 当变量数据类型为unsigned char时,编译器进行整形提升,将其提升为int。下面进行左移3,右移3操作。由于unsigned char仅为8位长,在32位寄存器中(向左补位)扩展为32位长。此时进行左移时,左移3位根本无法将位于寄存器“最右端”的“unsigned char中的任何一位”移出寄存器。然后进行右移操作,执行算术右移,将在寄存器左端补符号位,由于此时提升后的int为正数,符号位为0,寄存器数据右移3位前面全部补0。位移操作完毕,结果与位移前相同。然后,赋值给unsigned char型的i,截取末尾8位,i在寄存器里玩一圈儿又回来啦~
- 当变量数据类型为unsigned int时,编译器不进行整型提升,直接塞寄存器里,满满的32个1呀!此时左移3位,左边3个1没了。然后右移3位,由于是无符号型,执行逻辑右移,补上3个0。这就跟想象中的一样了。
这个操作即使改成“(i<<27)>>27”也达不到目的,词句只能将寄存器塞满1而已。需要使用“((unsigned char)(i<<3))>>3”才能正常移位。说通用点,正确的做法应该是“((i的数据类型)(i<<3))>>3”,或者使用类型与i相同的中间变量缓存下i<<3的值,再对中间变量>>3。
题外话:通过对比“((unsigned char)(i<<3))>>3”在VC编译器(VS2008)和INTEL编译器下的编译结果(均DEBUG),发现VC用了2个寄存器,而INTEL的仅用1个寄存器。反映出INTEL的编译器编码质量比VC的高。

浙公网安备 33010602011771号