【问题标题】:Bitwise shift *by* a negative number to reverse the bits in a number按位移位 *by* 负数以反转数字中的位
【发布时间】:2019-02-13 21:20:12
【问题描述】:

执行负数移位是否有效? 例如,如果我有以下代码:

#include <stdint.h>
uint32_t reverse_bits (uint32_t n)
{
    uint32_t result = 0;    
    for (int i = 0; i < 32; i++) 
    {
        uint32_t bit = n & (1 << i);
        bit <<= 31 - i * 2;
        result |= bit;
    }
    return result;
}

这是我可以期望在所有架构上工作的东西(具体来说,shift_amount &lt; 0 为真的表达式x &lt;&lt; shift_amt 的结果等同于x &gt;&gt; -shift_amt)?

注意:这不是关于对负数(即-1 &lt;&lt; 1)执行按位移位行为的问题。


这是完整的测试程序:

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
uint32_t reverse_bits (uint32_t n)
{
    uint32_t result = 0;
    for (int i = 0; i < 32; i++)
    {
        uint32_t bit = n & (1 << i);
        bit <<= 31 - i * 2;
        result |= bit;
    }
    return result;
}
void print_bits (uint32_t n)
{
    for (int i = 0; i < 32; i++)
        putchar(n & (1 << i) ? '1' : '0');
    putchar('\n');
}
int main ()
{
    for (int i = 0; i < 5; i++)
    {
        uint32_t x = rand();
        x |= rand() << 16;
        print_bits(x);
        print_bits(reverse_bits(x));
        putchar('\n');
    }
}

【问题讨论】:

  • 您假设 anunsigned long 是 32 位宽。
  • 是的,我将编辑示例代码以明确暗示 32 位(或更宽)无符号整数类型
  • 很好奇 - 你的编译器上的警告是什么意思? warning: left shift count is negative [-Wshift-count-negative](当我直接使用
  • 诚然我还没有测试过这个,也许我现在应该这样做。

标签: c shift


【解决方案1】:

C 标准声明在§ 6.5.7 paragraph 3: 中移动负数是明确未定义的行为

如果右操作数的值为负或大于或等于提升的左操作数的宽度,则行为未定义。

强调我的。

【讨论】:

  • 我想这与从 C 标准中得到的一样清楚,但由于它是“未定义的”,我想知道是否给定特定的目标架构(即你知道你要专门正在使用 X86-64) 编译器将生成的实际移位指令是否按照实际机器的规范运行。 (即,虽然 C 未定义此行为,但 X64 或 ARM 是否对行为有更具体的定义?)
  • 如果它是未定义的 (UB),你不能依赖任何东西,即使它看起来有效。下一个版本的编译器可以随意更改它,而不是您的代码中很难找到错误。
  • 我认为很容易检查负数并在需要时将另一个方向转换为正数。
  • ...并且明确禁止移位超过操作数的位宽。
  • @rsethc:不,硬件中的移位指令通常不考虑带符号的操作数。如果在汇编语言中将负值传递给移位指令,则典型行为是仅将低五位用于 32 位操作数。因此,只有 0 到 31 位的移位是可能的;负转变不是。
【解决方案2】:

如前所述,根据C standard 的第 6.5.7p3 节,按负值移位会调用undefined behavior

与其试图猜测何时可以摆脱负面转变,不如更改您的代码,这样您就不需要了。

屏蔽掉所需的位后,将其移回位置 0,然后将其移至所需位置。另外,确保将常量1 更改为1ul,这样您最终不会将有符号值移入符号位或超过int 的宽度。还要注意使用 sizeof 来避免硬编码幻数,例如 32。

unsigned long reverse_bits (unsigned long n)
{
    unsigned long result = 0;
    for (int i = 0; i < sizeof(unsigned long) * CHAR_BIT; i++)
    {
        unsigned long bit = ((n & (1ul << i)) >> i);
        unsigned long shift = (sizeof(unsigned long) * CHAR_BIT) - i - 1;
        result |= bit << shift;
    }
    return result;
}

【讨论】:

  • @wildplasser 好主意。已编辑。
  • 确实,首先将值一直转移到个位确实以更稳健的方式解决了问题。但是对于使用 sizeof,它很聪明,但通常我只想忽略类型长度中的额外字节,因为逻辑都是基于 32 位宽度的。
  • @rsethc 在 bithacks@stanford.edu 上有很好的 bitrev 示例。
  • @rsethc 您可以在上面的代码中轻松地将unsigned long 替换为uint32_t 并将sizeof(unsigned long) * CHAR_BIT 替换为32 并为您想要的类型获得相同的行为。
  • @rsethc 如果值的类型是有符号的,则将一位移入符号位是未定义的行为,特别是 1&lt;&lt;31 如果 int 是 32 位或更少。通过使用UL 后缀,它强制将值设为unsigned long,以便我们移入符号位。
猜你喜欢
  • 2013-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-05-24
  • 1970-01-01
  • 2011-06-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多