C 右移位运算符的异常行为

C 右移位运算符的异常行为,c,bit-manipulation,C,Bit Manipulation,我正在用C编写一个简单的代码(仅使用逐位运算符),它将指针指向无符号整数x,并在整数的二进制表示法中翻转第n个位置n。函数声明如下: int flip_bit (unsigned * x, unsigned n); 假设n介于0和31之间 在其中一个步骤中,我执行了右移操作,但结果与我预期的不同。例如,如果我做了0x8000000>>30,我得到的结果是0xfffffffe,即1000 0000。。。0000和1111111。。。1110分别以二进制表示。(预期结果为0000 0000…001

我正在用C编写一个简单的代码(仅使用逐位运算符),它将指针指向无符号整数
x
,并在整数的二进制表示法中翻转第n个位置
n
。函数声明如下:

int flip_bit (unsigned * x, unsigned n);
假设
n
介于0和31之间

在其中一个步骤中,我执行了右移操作,但结果与我预期的不同。例如,如果我做了
0x8000000>>30
,我得到的结果是
0xfffffffe
,即
1000 0000。。。0000和1111111。。。1110分别以二进制表示。(预期结果为
0000 0000…0010

我不确定我是如何或在哪里犯错误的。任何帮助都将不胜感激。谢谢

编辑1:下面是代码

#include <stdio.h>
#define INTSIZE 31

void flip_bit(unsigned * x,
          unsigned n) {

int a, b, c, d, e, f, g, h, i, j, k, l, m, p, q;
// save bits on the left of n and insert a zero at the end
a = * x >> n + 1;
b = a << 1;

// save bits on the right of n
c = * x << INTSIZE - (n - 1);
d = c >> INTSIZE - (n - 1);

// shift the bits to the left (back in their positions) 
// combine all bits
e = d << n;
f = b | e;

// Isolating the nth bit in its position
g = * x >> n;
h = g << INTSIZE;
// THIS LINE BELOW IS THE ONE CAUSING TROUBLE.
i = h >> INTSIZE - n;

// flipping all bits and removing the 1s surrounding 
// the nth bit (0 or 1)
j = ~i;
k = j >> n;
l = k << INTSIZE;
p = l >> INTSIZE - n;

// combining the value missing nth bit and 
// the one with the flipped one
q = f | p;
* x = q;
}
#包括
#定义INTSIZE 31
无效翻转位(无符号*x,
无符号(n){
int a,b,c,d,e,f,g,h,i,j,k,l,m,p,q;
//在n的左边保存位,并在末尾插入一个零
a=*x>>n+1;
b=a INTSIZE-(n-1);
//将位向左移动(回到其位置)
//合并所有位
e=d>n;
h=g>INTSIZE-n;
//翻转所有位并移除周围的1
//第n位(0或1)
j=~i;
k=j>>n;
l=k>INTSIZE-n;
//将缺少第n位的值与
//有翻转的那个
q=f | p;
*x=q;
}
当我运行
flip\u位(0x0000004e,0)
时,我得到了异常行为。有关右移操作的行上方有大写注释

也许有一种更短的方法可以做到这一点(不使用一千个变量),但这就是我现在所拥有的

编辑2:问题是我将变量声明为
int
(而不是
unsigned
)。然而,这是一个可怕的解决问题的方法@旧计时器建议返回
*x^(1u
或者这个呢

#include <stdio.h>
int main ( void )
{
    unsigned int x;
    x=0x12345678;
    x^=1<<30;
    printf("0x%08X\n",x);
}
我会这样做的

*x^=1<<n;

*x^=1这里的问题是您在签名的
int
上执行右移

根据本手册第6.5.7节:

5 E1>>E2的结果是E1右移位的E2位位置。如果E1具有无符号类型,或者如果E1具有有符号类型和非负 值,结果的值是 E1/2E2.如果E1具有有符号类型和负值,则 结果值由实现定义。

粗体部分是您的情况。您的每个中间变量的类型都是
int
。假设您的系统对负数使用2的补码表示,任何具有高位集的
int
值都被解释为负值

在本例中,您将看到的最常见的实现定义行为(事实上,这两者都是这样做的)是,如果在有符号的值上设置高位,那么在右移时1将被移入。这将保留值的符号,并使所有有符号和无符号的值的x>>n等于x/2n

您可以通过将所有中间变量更改为
无符号
,来解决此问题。这样,它们与
*x
的类型匹配,并且不会将1推到左侧

至于翻转位的方法,有一种简单得多的方法。您可以使用
^
运算符,这是按位异或运算符

根据C标准第6.5.11节:

4
^
运算符的结果是 操作数(即,当且仅当 转换后的操作数中正好有一个对应位为 设置)

例如:

  0010      1000
^ 1100    ^ 1101
------    ------
  1110      0101
请注意,您可以使用它创建位掩码,然后使用该位掩码翻转另一个操作数中的位

因此,如果要翻转位
n
,则取值1,左移
n
,将该位移动到所需位置,然后将该值与目标值异或以翻转该位:

void flip_bit(unsigned * x, unsigned n) {
    return *x = *x ^ (1u << n);
}

还请注意整数常量上的
u
后缀。这会导致常量的类型为
无符号
,这有助于避免您遇到的实现定义行为。

如果您稍微翻转一下,为什么不将1的xor与1相加,或将1与1相加,则任何与0相加的对象都是它自己。将1与1相加的对象都是它自己如果与0并用的任何东西都是零,与0并用的任何东西本身就是与1并用的任何东西都是颠倒的。与C无关。使用
无符号的int a,b,C,d,e,…
*x^(1u@old_timer:“我认为右移的符号扩展是实现定义的”-仅适用于有符号整数。大多数变量恰好是哪一个。仍然不确定在C中移动有符号数字是否被保险为符号扩展或“实现定义”。当然,此版本的gcc符号扩展了,这是您在混合有符号和无符号时遇到的问题。它是实现定义的。根据我的经验,gcc和MSVC对有符号类型进行算术右移。
1u
*x^=1<<n;
  0010      1000
^ 1100    ^ 1101
------    ------
  1110      0101
void flip_bit(unsigned * x, unsigned n) {
    return *x = *x ^ (1u << n);
}
return *x ^= (1u << n);