摘要:关于c/c++中由于CPU位宽造成的一个移位操作问题,b = ~a>>1中b的值的问题。
先看一段代码:
1: #include <stdio.h>
2:
3: int main()
4: {
5: unsigned char a = 0xAA ;
6: unsigned char b = 0x00 ;
7: b = ~a>>1 ;
8: printf("0x%X",b) ;
9: return 0 ;
10: }
VC中运行的输出结果是:0xAA,原书中解释是运算符~的优先级小于>>(当然这是不可能滴,毫无疑问~优先于>>),但是最后的结果感觉确实是运算>>再做~操作的。但是我个人觉得应该是0x2A,于是又在TC下运行,结果果然是0x2A,与自己的计算结果相同。
为什么会有不同结果呢?于是我在其他多种编译器上运行了这段代码,比如:gcc,lcc,bcc等,结果都是0xAA,那么问题又在哪儿呢?
即使将程序代码修改为:
1: b = (~a)>>1 ;
结果还是一样,说明确实是~运算符优先于>>。程序也是这样执行的。
最后我看了一下VC中的汇编代码:
1: 0041339E mov byte ptr [a],0AAh
2: 004133A2 mov byte ptr [b],0
3:
4: 004133A6 movzx eax,byte ptr [a]
5: 004133AA not eax
6: 004133AC sar eax,1
7: 004133AE mov byte ptr [b],al
终于弄懂了是怎么回事,在对a取反的时候先进行了一个自动的类型转换,转换结果为int型(从这里可以看出32位机CPU一次必须操作32个bit,不能只操作其中低8位),所以~a运算的结果就是0xFFFFFF55,那么对于有符号的数做右移操作实际上用的是SAR指令(不是SHR,两者的区别在后面说明),移位之后结果变为0xFFFFFFAA,然后在将其转换为unsigned char 类型赋值给变量b,那么b的值最后就是0xAA了。
那么为什么在TC上的结果会是0x2A了呢?那是因为TC中模拟的是8086体系的CPU结构(一个验证方法就是sizeof(int)==2),相当于运行于16bit位宽的计算机上,在这种结构中,CPU的寄存器位宽是16bit,但是有部分16bit寄存器可以分为两个8bit寄存器来独立操作,也就是说,在TC上运行时,~a操作不会进行类型转换,并且由于是unsigned char类型,结果就是0x55,然后直接对8位宽的0x55右移(采用SHR指令),结果就是0x2A了。
这个问题可以简化为:
1: unsigned int c = 0xFFAA;
2: int b ;
3: unsigned int d;
4: b = a>>1 ; /* 0xFFD5 */
5: d = c>>1 ; /* 0x7FD5 */
可以使用Keil验证上述想法,在Keil C166中采用16为的CPU和Keil for Arm中采用32位的CPU结果与上述描述的一致。
补充知识
;SHR(Shift Right): 逻辑右移 最高位移动至次高位,然后最高位补零
;SAR(Shift Arithmetic Right): 算术右移 这种情况下,最高位是符号位,最高位移动至次高位,最高位保持不变