C 右移位运算符的异常行为
我正在用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
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);