21

假设我有这样的功能:

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

每次从不同站点调用它bitCount都是非负的,并且在int. 我特别关心bitCount等于零的呼叫 - 那么它会正常工作吗?

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

4

6 回答 6

40

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

于 2009-06-11T12:31:41.497 回答
18

可以肯定的是,至少有一个 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 中看到的,英特尔将“return shift(42,0)”编译为“return 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;

...因此,如果您在一台机器上工作,当您弹出它们时,您压入堆栈的值可能不一样,那么,这种错过的优化可能是您遇到的最少的麻烦。

于 2009-06-11T12:43:51.547 回答
4

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

于 2009-06-11T11:24:48.287 回答
3

关于 arg << 0 或 arg >> 0 的正确性,没问题,绝对没问题。

关于最终的优化:当使用常量 what=0 和/或 bitcount=0 调用时,这不会简化为 >nop<,除非您将其声明为内联并选择优化(并且您选择的编译器了解内联是什么) .

因此,最重要的是,仅当参数的 OR 不为零时才通过有条件地调用函数来优化此代码(大约是我认为测试两个参数都非零的最快方法)。

于 2009-06-11T11:35:04.020 回答
3

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

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

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

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

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

编辑:零位的 AA 移位保证对被移位的东西没有影响。

于 2009-06-11T11:50:48.023 回答
1

为了使函数有点自我记录,您可能希望将 bitCount 更改为无符号以向调用者表示负值无效。

于 2009-06-11T15:33:12.323 回答