可以安全地自动加宽~3吗?

Mag*_*off 7 c++ casting bit-manipulation

在回答另一个问题时,我最终试图证明将操作数转换为~操作符,但是我无法想出一个不投出它会产生错误结果的场景.

我问这个澄清问题,以便能够清理其他问题,删除红色鲱鱼并保持最完整的相关信息.

问题是我们要清除变量的两个最低位:

offset = offset & ~3;
Run Code Online (Sandbox Code Playgroud)

这看起来很危险,因为~3将是一个int无论什么offset是的,所以我们最终可能会屏蔽不适合入位int的宽度.例如,如果int是32位宽并且offset是64位宽类型,可以想象该操作将丢失32个最高有效位offset.

然而,在实践中,这种危险似乎并未表现出来.相反,结果~3是符号扩展以填充宽度offset,即使offset是无符号时也是如此.

这种行为是由标准规定的吗?我问,因为看起来这种行为可能依赖于特定的实现和/或硬件细节,但我希望能够根据语言标准推荐正确的代码.


如果我尝试删除最不重要的位,我可以使操作产生不希望的结果.这是因为在~(1 << 31)二进制补码表示中的32位有符号整数(实际上是一个补码表示)中的结果为正,因此对结果进行符号扩展将使所有较高位未设置.

offset = offset & ~(1 << 31); // BZZT! Fragile!
Run Code Online (Sandbox Code Playgroud)

在这种情况下,如果int是32位宽并且offset类型较宽,则此操作将清除所有高位.

但是,在另一个问题中提出的解决方案似乎并没有解决这个问题!

offset = offset & ~static_cast<decltype(offset)>(1 << 31); // BZZT! Fragile!
Run Code Online (Sandbox Code Playgroud)

似乎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++!
Run Code Online (Sandbox Code Playgroud)

这种变化使原始危险变得相关.当位掩码无符号时,通过将所有高位设置为零来扩大反转位掩码,因此在反转之前获得正确的宽度非常重要.

这使我得出结论,有两种方法可以建议清除一些位:

1: offset = offset & ~3;

优点:简短易读的代码.

缺点:我不知道.但标准是否保证了这种行为?

2: offset = offset & ~static_cast<decltype(offset)>(3u);

优点:我理解这段代码的所有元素是如何工作的,我相信它的行为是由标准保证的.

缺点:它并没有完全滚动.


你能帮助我澄清选项1的行为是否得到保证,还是我不得不求助于推荐选项2?

M.M*_*M.M 4

它在符号-数值表示中无效。在 32 位整数的表示中,~3-0x7FFFFFFC。当它扩展到 64 位(带符号)时,该值将被保留,-0x7FFFFFFC. 所以我们不会说符号扩展发生在该系统中;并且您将错误地屏蔽所有 32 及更高位。

在二进制补码中,我认为offset &= ~3总是有效的。~3-4,所以无论 64 位类型是否有符号,您仍然会得到一个仅底部 2 位未设置的掩码。

然而,就我个人而言,我会尽量避免编写它,因为稍后检查我的代码是否有错误时,我必须再次经历所有这些讨论!(对于一个更随意的程序员来说,理解这里的复杂性有什么希望)。我只对无符号类型进行按位运算,以避免所有这些。