在回答another question时,我最终试图证明将操作数强制转换为~
运算符,但我无法想出一个不投出它会产生错误结果的场景。
我问这个澄清问题是为了能够清理其他问题,删除红色鲱鱼并保持最相关的信息完好无损。
问题在于我们要清除变量的两个最低位:
offset = offset & ~3;
这看起来很危险,因为无论~3
是什么int
都是offset
,所以我们最终可能会掩盖不适合int
& #39;的宽度。例如,如果int
为32位宽且offset
为64位宽类型,则可以想象此操作将丢失offset
的32个最高有效位。
然而,在实践中,这种危险似乎并未表现出来。相反,即使~3
未签名,offset
的结果也会进行符号扩展以填充offset
的宽度。
这种行为是否符合标准?我问,因为看起来这种行为可能依赖于特定的实现和/或硬件细节,但我希望能够根据语言标准推荐正确的代码。
如果我尝试删除最不重要的位,我可以使操作产生不希望的结果。这是因为~(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?
答案 0 :(得分:4)
在符号幅度表示中无效。在具有32位整数的表示中,~3
为-0x7FFFFFFC
。当它扩展到64位(带符号)时,将保留该值-0x7FFFFFFC
。所以我们不会说在该系统中会发生符号扩展;并且你将错误地掩盖所有32位及更高位。
在两个补码中,我认为offset &= ~3
始终有效。 ~3
为-4
,因此,无论64位类型是否已签名,您仍然会获得一个只有底部2位未设置的掩码。
但是,我个人试图避免编写它,因为当我稍后检查我的代码中的错误时,我将不得不再次讨论所有这些讨论! (以及一个更随意的程序员对这里的错综复杂有什么希望)。我只对无符号类型进行按位运算,以避免所有这些。