【问题标题】:Does the position of this bit-wise operator change the behavior?这个按位运算符的位置会改变行为吗?
【发布时间】:2019-10-18 15:17:34
【问题描述】:

这两行代码是等价的吗?

P1->OUT &= ~(uint8_t)(1<<1);

P1->OUT &= (uint8_t)(~(1<<1));

【问题讨论】:

  • @EugeneSh。我没有看到任何会在6.5.3.3 Unary arithmetic operators, paragraph 4 中的签名int 值上使用产生警告的东西:“~ 运算符的结果是其(提升的)操作数的按位补码(即结果中的每一位)当且仅当未设置转换后的操作数中的相应位时才设置)。整数提升是在操作数上执行的,结果具有提升的类型。......“如果它是等价的,我不会深入研究,不过,按照标准。那是语言律师的东西。
  • @AndrewHenle 我最初的评论(由于我没有测试它而被删除)是关于在将签名转换为 uint8_t 时有关“可能丢失数据”的警告,否则这是完全合法的
  • @EugeneSh。是的,gcc 对 IMO improper warnings 的内容有点过分热心了。所以它很可能会产生警告。

标签: c bit-manipulation language-lawyer bitwise-operators unsigned-integer


【解决方案1】:

这取决于P1-&gt;OUT 的类型和系统。 1 &lt;&lt; 1 的结果是 int 类型。

我正在考虑int n;而不是1 &lt;&lt; 1的一般情况

P1->OUT &= ~(uint8_t)(n);

~(整数提升)之前,操作数将再次扩大到int~ 将应用于int。结果将设置所有高位 8...k。如果P1-&gt;OUT 是 8 位宽,那没关系,但如果它有更多位,那么结果就不是你所期望的。

这个更糟糕:

P1->OUT &= (uint8_t)(~(n));

~ 操作数将再次应用于 int 并且 that 将转换为 uint8_t。现在如果~n 实际上是负数(设置了它的符号位) - 如果是~(1 &lt;&lt; 1) 它将是负数 - 在二进制补码实现中会很好,但在 1's- 中完全不正确补码和符号和大小的实现,因为位表示不一样。


进行位旋转的正确方法是始终使用 unsigned int 或更宽的二进制补码数:

P1->OUT &= ~(1U << n);

P1->OUT &= (~(1U << n)) & 0xFF;

避免产生有符号数的算术转换和整数提升。

【讨论】:

    【解决方案2】:

    TL;DR — 否!(它们并不总是相同,结果取决于P1-&gt;OUT 的类型。)

    我假设 2 的补码算术的正常情况。如果您遇到符号幅度或 1 的补码算术,您必须更加努力地思考,但结论可能是相同的(并不总是相同)。

    案例 1:

    • (1&lt;&lt;1) 是一个 int 值 (0x02)。
    • (uint8_t)(1&lt;&lt;1) 转换为 uint8_t,但值仍然是 0x02
    • ~(uint8_t)(1&lt;&lt;1)uint8_t 值再次转换为 int (usual arithmetic conversions) 并将按位反转运算符 ~ 应用于结果。

    假设一个 32 位的 int 类型,你得到 0xFFFFFFFD&amp;P1-&gt;OUT

    P1->OUT &= 0xFFFFFFFD;
    

    案例 2:

    • (1&lt;&lt;1) 是一个 int 值 (0x02)。
    • ~(1&lt;&lt;1)int 值 — 假设 32 位 int,则值为 0xFFFFFFFD
    • (uint8_t)~(1&lt;&lt;1) 转换为 uint8_t,将更高有效字节中的位归零。

    假设一个 32 位的 int 类型,你得到 0x000000FD&amp;P1-&gt;OUT

    P1->OUT &= 0x000000FD;
    

    因此,与P1-&gt;OUT 组合的值是不同的,但效果还取决于P1-&gt;OUT 的(未指定)类型。如果是uint8_t,则没有区别;如果是uint64_t,则可能存在巨大差异,但也取决于执行复合赋值之前P1-&gt;OUT 中的值。

    【讨论】:

    • "(uint8_t)~(1&lt;&lt;1) 转换为 uint8_t,将更高有效字节中的位归零。" 仅在 2 的补码系统中。虽然 OP 将遇到的所有系统很可能都是 2 的补码系统,但它提醒您从 intuint8_t 的转换不仅仅是“删除一些额外的位”,而是实际的算术运算。
    • 您的 case1 分析并非 100% 准确。 0xFFFFFFFD 不是 int 而是 unsigned int
    • @EugeneSh。 ——如果你仔细阅读,我实际上并没有说0xFFFFFFFDint;我刚刚说过,如果你的int 是32 位类型,你会得到0xFFFFFFFD 的值——没有指定它的类型。你对数字的类型是正确的,但我没有说你在估算我所说的。 (是的:这是对所写内容的令人痛苦的解释——但在我写它的时候它已经闪过我的脑海,我决定不再使我的答案复杂化。)当P1-&gt;OUTuint64_tint64_t 时会发生什么也很有趣.
    • @JonathanLeffler 那么这篇文章就不见了。你是说~(uint8_t)(1&lt;&lt;1)int,但在最后一行用 unsigned int 替换它。我同意考虑这里的各个方面可能会使答案过于复杂。
    • @EugeneSh.—我使用符号 0xFFFFFFFD 作为位模式的表示,其类型为int。所以,我可以添加一个演员表给P1-&gt;OUT &amp;= (int)0xFFFFFFFD;,但我认为它几乎没有帮助。在按位运算中使用带符号类型的整个业务变得令人担忧,并且根据标准很容易成为UB(§6.3.1.3 Signed and unsigned integers ¶3否则,新类型是有符号的,并且无法在其中表示值;要么结果是实现-defined 或实现定义的信号被引发。 等等。
    猜你喜欢
    • 1970-01-01
    • 2012-06-14
    • 2015-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多