C语言位运算遇到的坑

在Data Lab中有一个logicalShift函数给定一个值x和需要移动的位数n,要求只是用运算符:~ & ^ | + << >>,实现逻辑右移运算。思考了很久,然后我写出了如下的代码:

/* 
 * logicalShift - shift x to the right by n, using a logical shift
 *   Can assume that 0 <= n <= 31
 *   Examples: logicalShift(0x87654321,4) = 0x08765432
     Legal ops: !  & ^ | + << >>
 *   Max ops: 20
*   Rating: 3 
 */
int logicalShift(int x, int n) {
  return ( x >> n ) & ~( ( ( x & 0x80000000) >> n ) << 0x1 );
}

我的思路是:

  1. 如果x是负值,x >> n会让所有位右移n位,前n位补1,而在逻辑右移中前n位补0。解决这个问题,只需要把除去最高位的其他位置0,然后右移n位,再左移1位(事实上这里只要右移n-1位即可,但题目不能用减号,然后也要考虑n=0的特殊情况),再把这个数取反,和原来右移n位的值按位and,即可得到正确的结果。当n取0,右边的运算结果刚好是0,而左边是x,结果是x,满足。
  2. 如果x是正值,因为x & 0x80000000得到的结果为0,因此~( ( ( x & 0x80000000) >> n ) << 0x1) )为全1,不影响左边的计算结果。而对于正值,逻辑右移和算术右移是相同的。
    比如:
x n x >> n x & 0x80000000 (x & 0x80000000) >> n ( ( x & 0x80000000) >> n ) << 0x1) ~( ( ( x & 0x80000000) >> n ) << 0x1) ) Result
0x80000000 2 0xe0000000 0x80000000 0xe0000000 0xc0000000 0x3fffffff 0x20000000

一切看起来都很美好,但当运行上述代码的时候,发现输出的结果不是0x20000000而是0xa0000000.那么问题出现在哪?我尝试着打印(x & 0x80000000) >> n的值,发现结果居然是0x20000000,而不是理想中的0xe0000000。怎么回事,负数0x80000000右移2位后居然变成正数0x20000000,看起来有点逻辑右移的味道,所以这里的(x & 0x80000000)应该是unsigned,我试着把代码改成:

/* 
 * logicalShift - shift x to the right by n, using a logical shift
 *   Can assume that 0 <= n <= 31
 *   Examples: logicalShift(0x87654321,4) = 0x08765432
     Legal ops: !  & ^ | + << >>
 *   Max ops: 20
*   Rating: 3 
 */
int logicalShift(int x, int n) {
  int y = ( x & 0x80000000);
  int z = y >> n;
  return ( x >> n ) & ~( z << 0x1 );
}

然后,一切都没有问题了,顺利通过了。我又发现把最开始给出的错误代码中(x & 0x80000000)中的 0x80000000改成0x7fffffff,结果又被解释称int。
从这个问题,我得到的启发是:

  1. 只要位运算中出现了unsigned类型,无论另外一个是不是unsigned类型,得到的临时值都是unsigned。
  2. 负整数的16进制常量值(如0xffffffff表示-1)会被编译器当成unsigned类型,而非负整数则为int类型。
  3. 遇到位运算的问题时,尽量多做变量定义,不要想当然地错用了类型。
posted @ 2018-02-13 19:36  dreamnwx1  阅读(966)  评论(0编辑  收藏  举报