首页 文章

验证C / C签名的右移是特定编译器的算术运算?

提问于
浏览
18

根据C / C标准(see this link),C和C中的>>运算符不一定是有符号数的算术移位 . 由于位向右移位,所以由编译器实现是0(逻辑)还是符号位(算术)移入 .

对于为有符号整数实现逻辑右移的编译器,此代码是否会在编译时对ASSERT(fail)起作用?

#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 );

3 回答

  • 0

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

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

  • 6

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

    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_arithmetic 保证为 true ,那么每个编译器都会消除整个 if 子句并将 const bool negative 构造为死代码 .

    注意:代码改编自Mono的 encode_sleb128 功能:here .

    Update

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

    static_assert((Number(-1) >> 1) == Number(-1), "Arithmetic shift unsupported.");
    
  • 1

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

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

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

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

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

    看看装配输出并不是世界末日......有多少个不同的平台?由于这对性能至关重要,所以只需要为5种不同的架构查看1-3行汇编输出的“工作” . 这并不是说您必须潜入整个装配输出(通常!)才能找到您的 生产环境 线 . 这非常非常容易 .

相关问题