【问题标题】:Verifying that C / C++ signed right shift is arithmetic for a particular compiler?验证 C / C++ 有符号右移是否是特定编译器的算术?
【发布时间】:2009-10-20 22:36:10
【问题描述】:

根据 C/C++ 标准(see this link),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 );

【问题讨论】:

  • 对于那些拥有使用逻辑移位的机器的人来说,你的失败编译会做什么?为什么您的软件不能在这样的机器/编译器上使用?不管有符号数的右移是算术还是逻辑,编写代码不是更好吗?
  • 我正在通过位旋转使用无分支选择 (BFS)。它需要算术移位才能工作。我把 COMPILE_TIME_ASSERT( RIGHT_SHIFT_IS_ARITHMETIC );在 BFS 标头中。代码需要使用 RIGHT_SHIFT_IS_ARITHMETIC 定义来选择传统或无分支路径。由于分支预测错误,PS3/XBOX360 CPU 使用无分支代码可能会大大加快速度。
  • 顺便说一句,编译时失败的编译断言明确指出原因比仅仅让代码神秘地失败要好......基本上它会说这些例程不受此支持编译器(或 CPU)。
  • Adisak:一定要描述一下你从那个掩码添加技巧中得到的改进——在 int 单元上,我发现它有时只是对普通的旧 cmp/bge 的改进,具体取决于如何好吧,编译器设法在它周围交错了东西。这不是fsel 的巨大胜利。
  • 替代建议:如果您在编译时找不到执行此测试的好方法,那么在 main() 顶部进行快速运行时测试并崩溃并没有错如果运行时测试没有给出您想要的结果,则提供信息丰富的诊断/建议。它不如编译时错误好,但由于在测试中不能错过它,它避免了在启用不正确代码的情况下意外发布的可能性。

标签: c++ c math signed


【解决方案1】:

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

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

【讨论】:

【解决方案2】:

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

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.");

【讨论】:

  • 有很多代码(例如,使用掩码的无分支选择)仅适用于具有算术符号移位的编译器。优雅地模拟它们的算术移位可能会比原始代码慢得多,因此消除了“优化”。
  • 很公平。尽管如此,我的第二点仍然有效:不要依赖预处理器。查看更新。
  • static_assert() 是 C++11x,我们不能使用 boost。但是我所做的检查不依赖于预处理器,我可以执行以下操作,但它比我写的示例更难阅读和理解:typedef int CompileTimeAssertArithmeticShift[( (((signed int)-1)&gt;&gt;1) == ((signed int)-1) ) ? 1 : -1];
【解决方案3】:

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

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-08-19
    • 1970-01-01
    • 1970-01-01
    • 2015-09-05
    • 2022-11-04
    • 1970-01-01
    • 2019-02-19
    • 2020-02-25
    相关资源
    最近更新 更多