【问题标题】:Regarding bit masking in C. Why (~(~0 << N)) is preferred than ((1 << N) -1)?关于 C 中的位掩码。为什么 (~(~0 << N)) 比 ((1 << N) -1) 更受欢迎?
【发布时间】:2011-10-05 09:51:32
【问题描述】:

我确实知道 ~0 将评估最大字长位 1(因此需要考虑可移植性),但我仍然不明白为什么 ((1 不鼓励?

如果您使用第二种形式并遇到任何麻烦,请分享。

【问题讨论】:

  • 因为不是所有的 C 编译器/平台都对负数使用 2 补码。
  • @jv42 为什么适用?我在那里没有看到负数,除了 ~0 但它的负值不需要它的工作
  • 您能否提供不鼓励使用((1 &lt;&lt; N) -1) 的来源?
  • @harold:问题是如果你在(例如)1s' 补码中使用unsigned int x = ~0;~0 是所有位设置的,并且作为表示负零的有符号值(或者是陷阱表示)。所以当它转换为无符号时,结果是0,而不是UINT_MAXunsigned int x = ~0; 不关心可移植性,如果“可移植性”是指“包括非 2 的补码”。正确的做法是unsigned int x = -1;unsigned int x = UINT_MAX;
  • @harold:没错,但我不是在谈论 2 的补码。在不支持负零的 1 补码实现中,~0 是未定义的行为。所以严格符合代码不能写~0。不过,~0u 还可以。同样的考虑适用于符号幅度表示中的(1 &lt;&lt; N),适用于 1 的补码中的~0,因此就符号表示而言,它们同样糟糕。

标签: c bitmask


【解决方案1】:

看看这些行:

1. printf("%X", ~(~0 << 31) );
2. printf("%X", (1 << 31) - 1 );

1 行编译并按预期运行。

2 行给出警告integer overflow in expression

这是因为1 &lt;&lt; 31 默认被视为一个有符号 int,所以1 &lt;&lt; 31 = -2147483648 是可能的最小整数。

因此,休息 1 会导致溢出。

【讨论】:

  • 你可以在没有警告的情况下做(1u &lt;&lt; 31) - 1
  • @Juraj Blaho:是的,但这不是问题所在。 ((1 &lt;&lt; N) - 1) 根本不起作用。
  • 谢谢丹尼斯!是的,它确实会发出警告.. :) 但是在用作位掩码时仍然不能忽略溢出?
  • @MS.: 签名溢出是 UB,所以它可能会也可能不会工作,具体取决于编译器。 Gcc 基于有符号整数永远不会溢出这一事实进行了积极的优化。
  • @R.. 很好地说这个答案是完全错误的。很遗憾编译器没有警告1 &lt;&lt; 31。它是-2147483648 只是一个巧合。如果 int 是 32 位宽,1 &lt;&lt; 31 的结果在 int 中是不可表示的,就是这样,我们有 UB。
【解决方案2】:

第一种形式绝对不是首选,我什至会说它应该永远被使用。在不支持负零的补码系统上,~0 很可能是一个陷阱表示,因此在使用时会调用 UB。

另一方面,1&lt;&lt;31 也是 UB,假设 int 是 32 位,因为它会溢出。

如果您真的将 31 视为常数,0x7fffffff 是编写掩码的最简单和最正确的方法。如果您想要除 int 的符号位之外的所有内容,INT_MAX 是编写掩码的最简单和最正确的方法。

只要您知道位移位不会溢出,(1&lt;&lt;n)-1 就是设置最低n 位的掩码的正确方法。最好使用 (1ULL&lt;&lt;n)-1 后跟强制转换或隐式转换,以免担心符号问题和班次溢出。

但无论你做什么,都不要将~ 运算符与有符号整数一起使用。永远。

【讨论】:

  • 我从没想过 '1 signed int 可以采用从-21474836482147483647 的所有值,包括两者。问题:#define INT_MIN (-2147483647 - 1) 解决这个问题了吗?我问,因为这是 limits.h 在 gcc、tcc 和 icl 中所做的......
  • signed int 可以取该范围内的所有值的事实与1&lt;&lt;31 溢出的事实无关。这只是一个算术问题。 2^31 大于INT_MAX(假设为 32 位 int),因此它是一个溢出。
  • 那么看来我对移位操作不太了解。我曾经了解到1 &lt;&lt; 31,而不是2^31,是1向左移动了31个位置,并在右侧用0s填充。这将产生10000000 00000000 00000000 00000000 二进制或0x80000000 十六进制或-2147483648 作为有符号整数。我哪里做错了?
  • 在 C 中,x&lt;&lt;y 只是算术运算 x*2^y`。它与位移对应的事实被视为巧合。
  • 我不相信这会引发未定义的行为。 C99 6.3.1.3 §3 "Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised."
【解决方案3】:

我不鼓励两者,对有符号值进行移位或补码操作是一个坏主意。位模式应始终在无符号类型上生成,然后(如果甚至必要)转置到有符号计数器部分。然后使用原始类型也不是一个好主意,因为通常在位模式上你应该控制你正在处理的位数。

所以我总是会做类似的事情

-UINT32_C(1)
~UINT32_C(0)

它们是完全等价的,最后这只是为了使用UINT32_MAX 和 Co.

只有在你没有完全换档的情况下才需要换档,比如

(UINT32_C(1) << N) - UINT32_C(1)

【讨论】:

    【解决方案4】:

    我不会更喜欢另一个,但是我看到了很多 (1&lt;&lt;N) 的错误,其中值必须是 64 位,但“1”是 32 位(整数是 32 位),结果是N>=31 是错误的。 1ULL 而不是 1 会修复它。这是这种转变的危险之一。

    此外,未定义整数移位 CHAR_BIT*sizeof(int) 或更多位置(对于 long long(通常是 64 位),CHAR_BIT*sizeof(long long) 或更多位置类似)。因此,像这样向右移动可能更安全:~0u&gt;&gt;(CHAR_BIT*sizeof(int)-N),但在这种情况下,N 不能为 0。

    【讨论】:

    • ~0不是也有32位的问题吗?
    • @harold:如果整数是 32 位,~0 将是 32 位。但是你指的是什么问题?哦,可以理解,在~0u&gt;&gt;(CHAR_BIT*sizeof(int)-N)1 &lt;= N &lt;= CHAR_BIT*sizeof(int)。您可以将此表达式适当地扩展为 long-long。
    • 好吧,我的意思是,它没有理由选择 ~(~0
    • @harold:我只是指出了我在使用此类结构时遇到的一些问题。需要注意的事情。
    【解决方案5】:

    EDIT:更正了一个愚蠢的错误;并指出可能的溢出问题。

    我从未听说过一种形式优于另一种形式。两种形式都在编译时进行评估。我总是使用第二种形式,而且我从来没有遇到任何麻烦。这两种形式对读者来说都很清楚。

    其他答案指出第二种形式溢出的可能性。

    我觉得它们之间几乎没有什么可以选择的。

    【讨论】:

    • 如何在编译时评估第一个而第二个不能?
    【解决方案6】:

    为什么不鼓励
    ~0 是一个单周期操作,因此速度更快 ((1然后进行减法,这是一种算术运算。因此,由于减法,它会消耗很多周期,因此会产生不必要的开销。 p>

    更多
    此外,当您执行 ((1

    但是,如果您将 1 类型转换为 long 并执行 (((long)1

    【讨论】:

    • 我也很想知道转换 ((1
    • 我知道的任何平台都不是这样,但更重要的是,任何不是完全脑死的编译器都会使用零指令并只计算常数。
    • 哈罗德:什么不是真的?
      在 1
    • 移位和减法都不会消耗大量周期。不过,在石器时代,超过 1 的移动速度很慢。
    • 这意味着逻辑移位和算术运算具有相同的复杂性?我不觉得:)
    猜你喜欢
    • 1970-01-01
    • 2021-10-06
    • 2019-06-11
    • 2011-01-29
    • 2020-10-25
    • 2022-09-26
    • 2022-05-04
    • 2020-02-23
    • 2012-11-16
    相关资源
    最近更新 更多