18

根据 C / C++ 标准(请参阅此链接),C 和 C++ 中的 >> 运算符不一定是有符号数的算术移位。当位向右移动时,是否移入 0(逻辑)或符号位(算术)取决于编译器实现。

对于为有符号整数实现逻辑右移的编译器,此代码是否会在编译时执行断言(失败)?

#define COMPILE_TIME_ASSERT(EXP) \
    typedef int CompileTimeAssertType##__LINE__[(EXP) ? 1 : -1]

#define RIGHT_SHIFT_IS_ARITHMETIC \
    ( (((signed int)-1)>>1) == ((signed int)-1) )

// SHR must be arithmetic to use this code
COMPILE_TIME_ASSERT( RIGHT_SHIFT_IS_ARITHMETIC );
4

3 回答 3

6

在我看来很好!您还可以将编译器设置为发出汇编文件(或在调试器中加载已编译的程序)并查看它为哪个操作码发出signed int i; i >> 1;,但这不像您的解决方案那样自动。

如果您发现没有实现有符号数的算术右移的编译器,我想听听。

于 2009-10-20T22:39:43.777 回答
1

为什么要断言?如果您的编译器的移位运算符不适合您的需要,您可以通过对结果进行符号扩展来优雅地纠正这种情况。此外,有时运行时足够好。毕竟,编译器的优化器可以使编译时脱离运行时:

template <typename Number>
inline Number shift_logical_right(Number value, size_t bits)
{
    static const bool shift_is_arithmetic = (Number(-1) >> 1) == Number(-1);
    const bool negative = value < 0;
    value >>= bits;
    if (!shift_is_arithmetic && negative) // sign extend
        value |= -(Number(1) << (sizeof(Number) * 8 - bits));
}

static const bool可以在编译时进行评估,因此如果保证shift_is_arithmetictrue,每个值得一提的编译器都会消除整个if子句和const bool negative死代码的构造。

注意:代码改编自 Mono 的encode_sleb128函数:here

更新

如果你真的想在没有算术移位的机器上中止编译,你最好不要依赖预处理器。您可以使用static_assert(或BOOST_STATIC_ASSERT):

static_assert((Number(-1) >> 1) == Number(-1), "Arithmetic shift unsupported.");
于 2012-09-14T07:43:52.020 回答
0

从您的各种评论中,您谈到了使用这个跨平台。确保您的编译器保证在为平台编译时,其编译时运算符的行为与运行时运算符相同。

可以使用浮点数找到不同行为的示例。如果您要转换回 int,您的编译器是否以单精度、双精度或扩展精度进行常量表达式数学运算?如

constexpr int a = 41;
constexpr int b = (a / 7.5);

我的意思是,当你在这么多不同的架构上工作时,你应该确保你的编译器在运行时保证与编译时相同的行为。

编译器完全有可能在内部进行符号扩展,但不会在目标上生成预期的操作码。唯一可以确定的方法是在运行时进行测试或查看程序集输出。

看汇编输出不是世界末日……有多少不同的平台?由于这对性能至关重要,因此只需为 5 种不同架构查看 1-3 行汇编程序输出即可。并不是说您必须潜入整个程序集输出(通常!)才能找到您的行。这非常非常容易做到。

于 2012-09-17T17:53:48.253 回答