C语言中的位运算符

4

我已经写了一段交换位位置(源位和目标位)的代码,它可以正常工作。但是有没有更优化的代码来完成这个任务呢?

int bit_swap(int num, int sbit, int dbit)
{
if(num & (1 << sbit) == num & (1 << dbit))
return num;
else
return (num ^ ((1 << sbit) | (1 << dbit)));
}

这里的num是输入数字,sbit是源位位置,dbit是目标位位置。

有没有一种方法可以在不使用if和else的情况下将此代码写成一行?


3
你进行过简介分析吗?你是否进行了无需使用“if”的原始简介分析? - Karthik T
3
以下是链接中的内容翻译:交换位: 用异或方法问题:如何交换两个位?例如,x = 10110(二进制)和y = 01011(二进制),要将它们交换为x = 01011(二进制)和y = 10110(二进制)。解决方案:使用异或运算符(^)进行交换。其中^表示按位异或。它只在输入位不同时输出1,否则输出0。以下是实现代码:#define SWAP_BITS(x, i, j) (((x) >> (i)) & 1 != ((x) >> (j)) & 1) && ((x) ^= ((1U << (i)) | (1U << (j))))该代码将x的第i位和第j位互换。 我们可以在调用时使用此函数来执行上述示例中的位交换:SWAP_BITS(x, 4, 1);这将导致x和y值交换。注意,该代码仅适用于32位整数。对于其他数据类型和位数,需要相应地修改代码。 - leppie
1
@leppie: 这篇文章太好了.. 对我帮助很大.. 谢谢你! :) - Raghu Srikanth Reddy
1
对于交换操作,两个位都是源和目标,因此将一个命名为源,另一个命名为目标是具有误导性的。最好将它们命名为 pos1pos2 或类似的名称,如某些答案中所示。此外,在处理单个位时,使用无符号整数通常是一个好主意,至少如果您希望您的代码可移植。但这取决于您需要它做什么,只要知道您可能会遇到负值问题即可。 - Secure
2
我建议首先将警告级别调高,clang 报告:& has lower precedence than ==; == will be evaluated first [-Wparentheses]if(num & (1 << sbit) == num & (1 << dbit)) 中。 - Matthieu M.
1
在优化之前,先让它能够工作。例如:(255 & (1<<1)) != (255 & (1<<3)),导致两个位都被清零。使用xormask (1<<a)^(1<<b)可以帮助解决a==b的情况。 - Aki Suihkonen
3个回答

12

你犯了一个经典的错误,认为C语言中代码行数越少就意味着更优化的代码。

你真正应该做的是检查汇编输出并对代码进行性能分析,看看它是否是实际的瓶颈。

我倾向于首先优化可读性,只有在出现问题时才会考虑性能优化。因此,在我不那么谦虚的意见中,更可读的解决方案可能是这样的:

unsigned int bit_swap (unsigned int num, unsigned int pos1, unsigned int pos2) {
    // Swapping identical bit positions is a no-op.

    if (pos1 == pos2)
        return num;

    // Get masks from bit positions.

    unsigned int mask1 = 1 << pos1;
    unsigned int mask2 = 1 << pos2;

    // Get bit truth values.

    int bit1_was_set = ((num & mask1) != 0);
    int bit2_was_set = ((num & mask2) != 0);

    // Clear and set first bit (set only if second bit was originally set).

    num = num & ~mask1;
    if (bit2_was_set)
        num = num | mask1;

    // Do the same for other bit.

    num = num & ~mask2;
    if (bit1_was_set)
        num = num | mask2;

    // Return the value with swapped bits.

    return num;
}

尽管你的方法有更多的代码行,但你很可能会发现现在可用的超级优化编译器会在底层为你生成类似的代码。

你几乎可以确定的是,非 C 高手(也许包括你自己,六个月后)将能够更好地理解你的源代码,而不是单行多位运算符的变量。


6
没有条件版本。
int bit_swap(int num, int sbit, int dbit)
{
    int sval = !!(num & (1 << sbit));  // sets to 1 iff the s-bit is already set
    int dval = !!(num & (1 << dbit));  // sets to 1 iff the d-bit is already set

    int xorval = (sval ^ dval); // sets to 1 if (sval != dval), otherwise 0

    // so if xorval is 1, then it will toggle the bits at the S and D positions
    // otherwise, the expression below evalutes to "num" that was passed in
    return (num ^ ((xorval << sbit) | (xorval << dbit)));
}

2
如果sbitdbit指示了典型C实现中的一个int中的最高位,那么此代码将具有未定义的行为,因为1 << sbit1 << dbit会导致有符号int值溢出。通常最好使用无符号整数进行这样的位操作。 - Eric Postpischil
只有当 (sbit < 0) || (sbit >= (sizeof(int)*8) 时才会出现问题。对于 dbit 也是一样。假设为32位,31是最高有效位索引。 (1 << 31) 是 0x80000000,不会溢出。左移位对于有符号整数来说是可以的,但右移位是未定义的。上面没有右移位。 - selbie
1<<31是使用32位int时的溢出。C 2011(N1570)6.5.7 4:“如果E1具有带符号类型和非负值,并且E1*2 ** E2在结果类型中可表示,则该值为结果值;否则,行为未定义。”值2 ** 31在32位int中不可表示。 - Eric Postpischil
1
认为机器按照某种方式工作,因此C语义会跟随这种方式是一个陷阱。其中一件事情是,C规则以各种方式内置于优化器中,从而进行完全合法但意外的推导。一旦优化器推导出某段代码评估为1<<31,它可以做任何想做的事情,比如完全消除所有流向该评估的代码(在它之前的可观察效果)和所有从它流出的代码。 - Eric Postpischil
没有异议。在移位过程中改用无符号变量是正确的,但这并不会改变生成的目标代码(就我所知的机器而言)。但你的观点非常好。 - selbie
显示剩余4条评论

2
我想到了
unsigned swapBits(unsigned num, int sbit, int dbit)
{
  return ( num &               // All bits
    ~((1<<sbit) | (1<<dbit)))  // Except sbit and dbit
    | (((num>>sbit)&1)<<dbit)  // but with sbit moved to dbit
    | (((num>>dbit)&1)<<sbit); // and with dbit moved to sbit
}

我本来想用ARM,因为它的移位操作比较便宜。


1
很好,我期待这样的答案。 :) - Raghu Srikanth Reddy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接