【问题标题】:c bit operation bugc位操作错误
【发布时间】:2016-03-22 06:29:01
【问题描述】:

x 的来源已更新。这是一项任务,我被要求填写该功能。分配的要求是我不能更改参数类型或进行任何类型转换。所以我有这个问题。

我想使用 c 实现位计数。代码如下:

int bitCount(int x) {
    int mask1 = 0x55555555;
    int mask2 = 0x33333333;
    int mask3 = 0x0f0f0f0f;
    int mask4 = 0x00ff00ff;
    int mask5 = 0x0000ffff;
    //x = 0xffffffff;
    if (x != 0xffffffff) {
        exit(0);
    }
    printf("%x\n", x);
    x = (x & mask1) + ((x >> 1) & mask1);
    printf("%x\n", x);
    x = (x & mask2) + ((x >> 2) & mask2);
    printf("%x\n", x);
    x = (x & mask3) + ((x >> 4) & mask3);
    printf("%x\n", x);
    x = (x & mask4) + ((x >> 8) & mask4);
    printf("%x\n", x);
    x = (x & mask5) + ((x >> 16) & mask5);
    printf("%x\n", x);

    return x;
}

x = -1(或十六进制的0xffffffff)时,答案应该是0x20。但实际上输出是:

ffffffff
aaaaaaaa
24444444
6080808
e0010
1e

如果我取消注释该行 代码中"x = 0xffffffff",输出变为:

ffffffff
aaaaaaaa
44444444
8080808
100010
20

操作系统是 Mac OS X。gcc 版本是:

gcc --version

配置:

--prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin14.5.0
Thread model: posix

为什么?

【问题讨论】:

  • x 究竟从何而来?
  • 另外,版本字符串清楚地表明这实际上不是 gcc,而是 clang。我假设 OSX 别名是 gcc。
  • 您需要提供minimal reproducible example
  • @PaulR 当然没有最小、完整和可验证的例子,因为这是 UB :D
  • @AnttiHaapala:好吧,我会满足于最小和完整。 ;-)

标签: c macos gcc bit


【解决方案1】:

首先,负整数的右移是实现定义的:

E1 >> E2 的结果是 E1 右移 E2 位位置。如果E1 具有无符号类型或E1 具有带符号类型和非负值,则结果的值是整数 E1 / 2^E2 商的一部分。如果 E1 具有带符号类型和负值,则结果值是实现定义的。

但是,假设我们使用的是 x86,并且负整数的右移就像它是 2 的补码表示一样工作,并产生正确的 32 位值。如果int x = 0xFFFFFFFF 那么x & mask10x55555555 并且(x >> 1) & mask 也是0x55555555,两者都是正数;然后他们的总和是0xAAAAAAAA

x = (x & mask1) + ((x >> 1) & mask1);
如果int 是 32 位宽,

会导致有符号整数溢出,因此您的代码具有未定义行为。你应该必须使用unsigned int来进行这种位旋转而不是int

我无法在我的计算机上重现您的错误,但我可以通过在溢出添加后屏蔽 x 的符号位来获得相同的输出:

x = (x & mask1) + ((x >> 1) & mask1);
x = x & 0x7FFFFFFF;

只有通过此更改,我才能获得您观察到的错误输出:

ffffffff
aaaaaaaa
24444444
6080808
e0010
1e

当 64 位处理器变得越来越普遍时,这种 32 位整数的未定义行为增加了很多 - 根据 C 标准,允许编译器将 64 位寄存器用于 32 位 有符号 整数,因为它们永远不会溢出,如果你溢出它们,你的“32 位”int 变量甚至可能最终包含 64 位值 .

【讨论】:

    【解决方案2】:

    我正要上床睡觉,但后来被这个答案打扰了,所以我起床了。我不关心初始化/不初始化。很难知道,因为你的代码只有 1/2,但我认为更多的是关于有符号/无符号的差异。

    您正在右移,这对于有符号数保留负位;但对于未签名的不会。看起来您的数字集具有较小的值是在轮班中使用无符号数字的结果。

    我注意到您没有指定x 的类型。这对C 来说真的很奇怪,你真的应该在那个变量前面放一个类型。

    --- 原帖如下---

    x = 0xffffffff;
    

    您清楚地将变量 x 初始化为已知的特定值。

    if (x != 0xffffffff) {
    

    您清楚地检查未初始化变量x 中的任何内容与已知的特定值。简而言之,在程序运行之前,您不知道变量 x 的内存中有什么,这就是您要阅读的内容。

    你只是“走运了”,得到了一些“看起来不错”的输出,但如果你以不同的方式运行它,或者在不同的机器上运行它,你很可能根本不会得到相同的输出。

    【讨论】:

    • 虽然这可能是真的,也可能不是(我们不知道 x 来自哪里,因此也不知道它是否已初始化),但我看不出它回答了问题全部。第一个printf也确认x确实是0xffffffff,不是吗?
    • @Dolda2000 你是对的,但我什至不确定x 在第二种情况下是int。我更新了帖子。我认为这与缺少类型以及在这两种情况下类型的推断方式不同有关。基本上在一种情况下,它似乎是未签名的,而另一种情况是已签名的。
    • 是的,我也有类似的怀疑,但如果不知道x 是什么,确实很难说。再说一次,分配给x 不应更改其类型,并且掩码应删除任何扩展符号位,使signed/unsigned 区别无关紧要。
    • 负数右移实际上取决于实现
    • 有符号整数溢出
    猜你喜欢
    • 1970-01-01
    • 2012-02-11
    • 2018-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多