【问题标题】:Compile-time checking if right shift is arithmetic on signed types编译时检查右移是否是有符号类型的算术运算
【发布时间】:2011-02-25 22:16:15
【问题描述】:

我想知道在编译时对有符号类型(例如 -2 >> 1 是否为 -1)进行操作时检查右移是否是算术的最便携方法是什么。

我的想法是在编译时以某种方式检查这一点并能够检测到这一点,因此我可以编译不同版本的函数(取决于运算符 >> 是否真的是算术移位)。

通过阅读主题 Verifying that C / C++ signed right shift is arithmetic for a particular compiler?我想到了初始化一个标志

static const bool is_arithmetic_rs = (((signed int)-1)>>1) == ((signed int)-1));

并像这样在运行时对其进行测试:

if (is_arithmetic_rs) {
  // some fast algorithm using arithmetic right shifts (using >> operator)
} else {
  // the same algorithm without arithmetic right shifts (much slower)
}

但是,如果可能的话,我希望每次都避免这种分支。为简单起见,假设我想实现一个可移植的算术右移;如果我必须在每次调用函数时检查这一点,这将对性能产生巨大影响,所以如果可能的话,我想在编译时进行。

如果不存在进行此检查的可移植方式,是否有办法通过尽最大努力进行检查,例如使用 ifdefs 检查特定编译器/平台?

【问题讨论】:

  • 您是否遇到过很多编译器都会出错?帮我们一个忙,记录他们的名字。
  • 到目前为止我没有找到任何东西,但我没有找到任何东西。如果我找到任何我会在这里创建一个列表。
  • 我认为这几乎不是问题,就像补码、符号/大小、填充位、EBCDIC 以及标准允许的许多其他荒谬的事情一样。
  • 任何智能编译器都会在编译时确定表达式的结果,并优化测试和未使用的分支。别担心。
  • @HansPassant “弄错”是什么意思?这种转变的行为是实现定义的

标签: c++ c portability bit-manipulation compile-time


【解决方案1】:

执行此类检查的最佳方法是例如GNU autotools做:

  • 在您的目标平台上编译一个小程序并测试会发生什么

  • 在头文件中设置合适的#define

  • 在源文件中包含该头文件

  • (可选)使用适当定义的宏,这样您的代码就不会因为每件小事都被 #ifdef 指令弄乱了。

  • 编译你的主项目

这样您就不必创建具有支持的功能的表格以及每个硬件平台和操作系统的各种怪癖 - 更不用说它们的组合了。但是,如果您不在目标上构建代码,您必须将第一步替换为为您的目标预先提供的表/功能列表。

您可能应该看看广泛使用的构建系统,例如 GNU autotools 或 CMake,以便重用现有的宏和特定于平台的信息,避免必须创建自己的并因此重新发明轮子。

顺便说一句,如今任何体面的编译器都应该优化带有常量表达式的简单测试,因此在必要时使用运行时测试(可能通过宏)不会对性能造成太大影响。您应该测试和分析您的代码以找出答案。

【讨论】:

  • 平台是什么意思?右移的行为是否取决于特定的架构、操作系统或两者兼而有之?
  • @leden:我只是为所有操作系统/CPU 可能不同的情况提供了一种通用方法。我不知道 C 中的移位操作是否会在不同的 CPU 之间有所不同,尽管我认为它不会在同一 CPU 上的编译器或操作系统之间有所不同。
  • 我不同意,我会尽量尊重地这样做。 GNU autotools 本身有许多问题,我在这里尽量不吐槽,但也许它们通常使用方式的最糟糕的方面是对可以平等检查的事物进行完全向后测试 - 或者 -在源代码级别使用预处理器或常量表达式的简单条件更容易。 OP的问题肯定是这样的。
  • 如果 leden 真的在这个非常奇怪的硬件上运行,GNU 自动工具在那里可用的几率有多大??
【解决方案2】:

可以通过使用预处理时间测试来避免分支

#if ((-1)>>1) == (-1))
...
#else
...
#endif

【讨论】:

  • 你可以 #define IS_ARITHMETIC_RS (-2 >> 1 == -1) 然后用 #if IS_ARITHMETIC_RS ... #else 乱扔你的代码
  • 这就是我一直在寻找的。这种方法有什么可能的问题吗?
  • 预处理器是否保证提供与编译器完全相同的结果?我非常怀疑,尤其是在交叉编译时。
  • 它“应该”,也在交叉编译中。但是您的怀疑是有道理的,因为预处理器通常是与编译器弱耦合的单独工具。一种更安全的方法是使用与纯 C 相同的条件表达式(“if (...) { ...”而不是“#if ...”),并让编译器检测到两个分支之一不会' t 永远不会被执行,因为测试表达式是常量。大多数编译器不生成代码。许多还会产生警告......一些编译器确实会产生无用的代码:(
  • ...当然,没有预处理器的解决方案只能在函数内部工作(而预处理器也可以应用于定义、初始化器、包含...),并且两个分支都必须在语法上正确.
【解决方案3】:

实际上更多的是评论而不是答案(但显然我没有信誉)

这里的一些答案使用预处理器检查,例如

#if ((-1)>>1) == (-1))

就我个人而言,我不相信预处理器会告诉我编译器生成什么样的代码。

【讨论】:

    【解决方案4】:

    您是否确实验证了您的编译器在可用时没有将除法优化为算术移位?

    否则我认为你可以使用模板。

    template <bool B>
    void do_work();
    
    template <>
    void do_work<true>()
    {
        // Do stuff with H/W.
    }
    
    template <>
    void do_work<false>()
    {
        // Do slow stuff with S/W.
    }
    
    do_work<(-2 >> 1) == -1>();
    

    然后让它更漂亮地与内联函数一起使用:

    inline real_do_work()
    {
        do_work<(-2 >> 1) == -1>();
    }
    

    【讨论】:

    • 除法在我的情况下是有问题的,因为它向零舍入,但我实际上需要向下舍入。顺便说一句,模板的解决方案非常好,我想知道这个与标准 #if 相比有什么优点/缺点?
    • @leden 它基本上将逻辑划分为两个模板函数,而不是使用预处理器在每种情况下使用#if 放置正确的代码。
    【解决方案5】:

    试试这个:

    #define SAR(x,y) ((x)>=0) ? ((x)>>(y)) : (~(~(x)>>(y)))
    

    假设 CPU 正常,一个好的编译器会将其优化为 ((x)&gt;&gt;(y))

    欢迎反馈哪些编译器好。

    【讨论】:

    • gcc -O3 不会优化这个(版本 4.5.2),而 clang -O1 会(版本 2.8)
    • 令人沮丧。它应该是微不足道的。假设 &gt;&gt; 是有符号类型的算术运算,~&gt;&gt; commute,经过简化后,两个分支包含相同的代码。
    【解决方案6】:

    启发 Giuseppe'sR..'s 答案:

    #if -2 >> 1 == -1
    #define rshift_ar(X, Y) ((X) >> (Y))
    #elif ~(~(-2) >> 1) == -1
    #define rshift_ar(X, Y) ((X) >= 0 ? (X) >> (Y) : ~(~(X) >> (Y)))
    #else
    #error "unsupported shifting semantics"
    #endif
    

    【讨论】:

    • @imallett:已经有几年了,但我认为你是对的 - 结束括号应该放在 == 之前
    • 我觉得还是不对。 ~-21,所以&gt;&gt; 结果无论如何都是0。此外,您的宏不适用于正数。
    • @imallett:条件应该没问题~(~(-2) &gt;&gt; 1) == ~(1 &gt;&gt; 1) == ~0 == -1
    • 重点是它总是为真,不管班次类型如何。我看到对于简单参数的转变已经修复:)
    • @imallett:关键是它总是正确的,不管班次类型如何——这就是它排在第二位的原因;这是一个健全性检查(例如,符号幅度表示应该失败)
    【解决方案7】:

    所有这些预处理器的魔法对于任何体面的编译器都是无用的。您是否验证了上面给出的代码确实在其输出中生成了一个分支?我非常怀疑它,因为static const bool 可以被评估为编译时间常数,所以if (is_arithmetic_rs)false-部分将被编译器消除,高于-O1。另见我的回答here

    此外,我怀疑预处理器的输出是否能保证与编译器的输出相同,尤其是在具有不同转换的平台之间进行交叉编译时。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-11-04
      • 2023-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-17
      相关资源
      最近更新 更多