今天在写代码的时候,遇到了一点小问题,代码如下。
int clearbits(unsigned long x, unsigned short p, unsigned short n)
{
unsigned long high = (~0) << (32 - p + 1);
unsigned long low = (~0)>> (p + n - 1);
unsigned long griddle = ~0 & (high | low);
printf("griddle = 0x%08x, low = 0x%08x, high = 0x%08x\n", griddle, low, high);
return x & griddle;
}
int main(int argc, char **argv)
{
unsigned long x = 0xABCD;
printf("0x%08x\n", clearbits(x, 5, 5));
return 0;
}
函数的意思是对x的某些位数进行清除,但是实际运行的时候,结果与预期很不一样,在代码里加了打印进行调试,函数调用时,有如下打印。
griddle = 0xffffffff, low = 0xffffffff, high = 0xf0000000
low的值并非我想要的,即数值0xffffffff右移32 - p - n + 1位,并且高位补零,那么问题出在哪里呢?只好在反汇编下一看究竟。
unsigned long low = (~0) >> (p + n - 1);
000C3D48 movzx eax,word ptr [p]
000C3D4C movzx ecx,word ptr [n]
000C3D50 lea ecx,[eax+ecx-1]
000C3D54 or edx,0FFFFFFFFh
000C3D57 sar edx,cl
000C3D59 mov dword ptr [low],edx
反汇编下,发现右移操作使用的是000C3D57
sar edx,cl ,即进行了算术右移,而并非逻辑右移,因此按照有符号数,将高位补1,而不是补0,这样就与我的预期不符了。
对代码进行如下修改
int clearbits(unsigned long x, unsigned short p, unsigned short n)
{
unsigned long high = (~0) << (32 - p + 1);
unsigned long low = (unsigned long)(~0)>> (p + n - 1);
unsigned long griddle = ~0 & (high | low);
printf("griddle = 0x%08x, low = 0x%08x, high = 0x%08x\n", griddle, low, high);
return x & griddle;
}
这样,继续运行,得到的结果就和预期相符了。
griddle = 0xf03fffff, low = 0x003fffff, high = 0xf0000000
反汇编再确认一下,使用的是000C3D43 shr edx,cl ,即进行了逻辑右移,这样高位会补0,结果就和我的预期是相符的了。
unsigned long low = (unsigned long)(~0) >> (p + n - 1);
000C3D34 movzx eax,word ptr [p]
000C3D38 movzx ecx,word ptr [n]
000C3D3C lea ecx,[eax+ecx-1]
000C3D40 or edx,0FFFFFFFFh
000C3D43 shr edx,cl
000C3D45 mov dword ptr [low],edx
由这个问题可以看出,如果代码写成unsigned long low = (~0) >> (p + n - 1); 编译器会先认为对0取反后是一个有符号数,继而对其进行了算术右移操作。那么,如果我们不使用~0,而直接使用0xFFFFFFFF呢,即unsigned long low = (0xFFFFFFFF) >> (p + n - 1);,经过测试,这样会认为0xFFFFFFFF是一个无符号数,并随后进行逻辑右移操作。
这里,为了安全,涉及到右移操作时,还是需要对操作数的类型进行考量。