在回答 another question 时,我最终试图证明将操作数转换为 ~
是合理的运算符,但我无法想出不转换它会产生错误结果的场景。
我问这个澄清问题是为了能够清理另一个问题,去除转移注意力的问题,只保留最相关的信息。
问题是我们要清除变量的最低两位:
offset = offset & ~3;
这看起来很危险,因为 ~3
将是 int
不管怎样offset
是的,所以我们最终可能会屏蔽不适合 int
的位的宽度。例如,如果 int
是 32 位宽并且 offset
是 64 位宽的类型,可以想象这个操作会丢失 offset
的 32 个最高有效位。 .
然而,在实践中,这种危险似乎并没有表现出来。相反,~3
的结果符号扩展以填充 offset
的宽度,即使 offset
未签名。
这种行为是标准强制要求的吗?我问是因为似乎这种行为可能依赖于特定的实现和/或硬件细节,但我希望能够根据语言标准推荐正确的代码。
如果我尝试删除 32. 最低有效位,我可以使操作产生不希望的结果。这是因为 ~(1 << 31)
的结果在二进制补码表示(实际上是一个补码表示)的 32 位带符号整数中将为正数,因此对结果进行符号扩展将使所有高位不设置。
offset = offset & ~(1 << 31); // BZZT! Fragile!
在这种情况下,如果 int
是 32 位宽并且 offset
是一个更宽的类型,这个操作将清除所有的高位。
但是,在另一个问题中提出的解决方案似乎并没有解决这个问题!
offset = offset & ~static_cast<decltype(offset)>(1 << 31); // BZZT! Fragile!
似乎1 << 31
将在类型转换之前进行符号扩展,因此无论是否decltype(offset)
有符号或无符号,此转换的结果将设置所有高位,这样再次操作将清除所有这些位。
为了解决这个问题,我需要在扩大之前使数字无符号,方法是使整数文字无符号( 1u << 31
似乎有效)或将其转换为 unsigned int
:
offset = offset &
~static_cast<decltype(offset)>(
static_cast<unsigned int>(
1 << 31
)
);
// Now it finally looks like C++!
此更改使原始危险变得相关。当位掩码无符号时,反转的位掩码将通过将所有高位设置为零来加宽,因此在反转之前拥有正确的宽度很重要。
这让我得出结论,有两种方法可以推荐清除一些位:
1:offset = offset & ~3;
优点:简短、易于阅读的代码。
缺点:据我所知没有。但是标准保证了行为吗?
2:offset = offset & ~static_cast<decltype(offset)>(3u);
优点:我理解这段代码的所有元素是如何工作的,而且我相当有信心标准保证它的行为。
缺点:它不完全是舌头滚动。
你们能帮我弄清楚选项 1 的行为是否得到保证,或者我是否必须求助于推荐选项 2?
最佳答案
它在符号大小表示中无效。在具有 32 位整数的表示中,~3
是 -0x7FFFFFFFC
。当它扩展到 64 位(有符号)时,该值将被保留,-0x7FFFFFFFC
。所以我们不会说符号扩展发生在那个系统中;并且您将错误地屏蔽掉所有 32 位和更高位。
在二进制补码中,我认为 offset &= ~3
始终有效。 ~3
是 -4
,因此无论 64 位类型是否已签名,您仍然会得到一个只有底部 2 位未设置的掩码。
但是,我个人会尽量避免编写它,因为稍后检查我的代码是否存在错误时,我将不得不再次进行所有这些讨论! (更随便的编码员有什么希望理解这里的复杂性)。我只对无符号类型进行按位运算,以避免所有这些。
关于c++ - ~3 可以安全地自动加宽吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25381581/