位移零位是否正常工作?

时间:2009-06-11 11:22:13

标签: c++ bit-manipulation bit-shift

说我有这样的功能:

inline int shift( int what, int bitCount )
{
    return what >> bitCount;
}

每次bitCount为非负且在int中的位数内时,将从不同的网站调用它。我特别关注bitCount等于零的呼叫 - 它会正常工作吗?

在编译其调用站点时编译器是否有可能看到函数的整个代码会将bitCount等于零的调用减少为无操作?

6 个答案:

答案 0 :(得分:22)

According to K&R“如果右操作数为负数,或者大于或等于左表达式类型中的位数,则结果未定义。” (A.7.8)因此>> 0是身份右移,完全合法。

答案 1 :(得分:17)

某些至少有一个C ++编译器会识别这种情况(在编译时知道0时)并使其成为无操作:

<强>来源

inline int shift( int what, int bitcount)
{
  return what >> bitcount ;
}

int f() {
  return shift(42,0);
}

编译器开关

icpc -S -O3 -mssse3 -fp-model fast=2 bitsh.C

英特尔C ++ 11.0程序集

# -- Begin  _Z1fv
# mark_begin;
       .align    16,0x90
        .globl _Z1fv
_Z1fv:
..B1.1:                         # Preds ..B1.0
        movl      $42, %eax                                     #7.10
        ret                                                     #7.10
        .align    16,0x90
                                # LOE
# mark_end;
        .type   _Z1fv,@function
        .size   _Z1fv,.-_Z1fv
        .data
# -- End  _Z1fv
        .data
        .section .note.GNU-stack, ""
# End

正如您在..B1.1中看到的那样,英特尔将“返回班次(42,0)”编译为“返回42”。

英特尔11还剔除了这两种变化的转变:

int g() {
  int a = 5;
  int b = 5;
  return shift(42,a-b);
}

int h(int k) {
  return shift(42,k*0);
}

对于在编译时移位值不可知的情况......

int egad(int m, int n) {
  return shift(42,m-n);
}

......这种转变是无法避免的......

# -- Begin  _Z4egadii
# mark_begin;
       .align    16,0x90
        .globl _Z4egadii
_Z4egadii:
# parameter 1: 4 + %esp
# parameter 2: 8 + %esp
..B1.1:                         # Preds ..B1.0
        movl      4(%esp), %ecx                                 #20.5
        subl      8(%esp), %ecx                                 #21.21
        movl      $42, %eax                                     #21.10
        shrl      %cl, %eax                                     #21.10
        ret                                                     #21.10
        .align    16,0x90
                                # LOE
# mark_end;

...但至少它是内联的,因此没有呼叫开销。

奖金组装:挥发性很贵。来源......

int g() {
  int a = 5;
  volatile int b = 5;
  return shift(42,a-b);
}

...而不是无操作,编译为......

..B3.1:                         # Preds ..B3.0
        pushl     %esi                                          #10.9
        movl      $5, (%esp)                                    #12.18
        movl      (%esp), %ecx                                  #13.21
        negl      %ecx                                          #13.21
        addl      $5, %ecx                                      #13.21
        movl      $42, %eax                                     #13.10
        shrl      %cl, %eax                                     #13.10
        popl      %ecx                                          #13.10
        ret                                                     #13.10
        .align    16,0x90
                                # LOE
# mark_end;

...所以,如果你在一台机器上工作,当你弹出它们时,你在堆栈上推送的值可能会不一样,那么这个错过的优化可能是你遇到的麻烦最少的。

答案 2 :(得分:4)

它可以在任何广泛使用的架构上正常工作(我可以担保x86,PPC,ARM)。除非函数内联,否则编译器将无法将其减少为noop。

答案 3 :(得分:3)

如果编译器在编译时知道bitCount值为零,则编译器只能执行此优化。这意味着传递的参数必须是常量:

const int N = 0;
int x = shift( 123, N );

C ++当然允许执行这样的优化,但我不知道有任何编译器这样做。编译器可以采用的替代方法:

int x = n == 0 ? 123 : shift( 123, n );

在大多数情况下会是一种悲观情绪,我无法想象编译器编写者会实现这样的事情。

编辑:保证零位的AA偏移不会对被移位的东西产生影响。

答案 4 :(得分:2)

关于arg&lt;&lt;的正确性0或arg&gt;&gt; 0,没问题,绝对没问题。

关于最终的优化: 这不会减少到&gt; nop&lt;当使用常量what = 0和/或bitcount = 0调用时,除非您将其声明为内联并选择优化(并且您选择的编译器理解内联是什么)。

所以,最重要的是,只有当参数的OR不为零时才有条件地调用函数来优化这段代码(关于我试图测试两个args都不为零的最快方法)。

答案 5 :(得分:1)

要使函数有点自我记录,您可能需要将bitCount更改为unsigned以表示负值无效的调用者。

相关问题