GCC 4.4:避免在gcc中对switch/case语句进行范围检查?

ale*_*cco 15 c assembly switch-statement gcc4.4

这只是4.4之前的GCC版本的问题,这在GCC 4.5中已得到修复.

是否有可能告诉编译器交换机中使用的变量是否适合所提供的case语句?特别是如果它是一个小范围并且有一个跳转表生成.

extern int a;
main()
{
        switch (a & 0x7) {   // 0x7  == 111  values are 0-7
        case 0: f0(); break;
        case 1: f1(); break;
        case 2: f2(); break;
        case 3: f3(); break;
        case 4: f4(); break;
        case 5: f5(); break;
        case 6: f6(); break;
        case 7: f7(); break;
        }
}
Run Code Online (Sandbox Code Playgroud)

我尝试xor'ing到低位(作为例子),使用枚举,使用gcc_unreachable()无济于事.生成的代码总是检查变量是否在范围内,添加无条件分支条件并移走跳转表计算代码.

注意:这是在解码器的最内层循环中,性能非常重要.

看来我不是唯一 一个.

没有办法告诉gcc永远不会采用默认分支,尽管它可以省略默认分支,如果它可以证明该值永远不会超出先前的条件检查范围.

那么,你如何帮助gcc证明变量适合并且上面的例子中没有默认分支?(当然,不添加条件分支.)

更新

  1. 这是在OS X 10.6 Snow Leopard上使用GCC 4.2(默认来自Xcode.)它没有发生在Linux中的GCC 4.4/4.3(由Nathon和Jens Gustedt报道).

  2. 示例中的函数是为了可读性,认为这些是内联的或只是语句.在x86上进行函数调用是很昂贵的.

    此外,如注释中所述,该示例属于数据循环(大数据).

    使用gcc 4.2/OS X生成的代码是:

    [...]
    andl    $7, %eax
    cmpl    $7, %eax
    ja  L11
    mov %eax, %eax
    leaq    L20(%rip), %rdx
    movslq  (%rdx,%rax,4),%rax
    addq    %rdx, %rax
    jmp *%rax
    .align 2,0x90
    L20:
    .long   L12-L20
    .long   L13-L20
    .long   L14-L20
    .long   L15-L20
    .long   L16-L20
    .long   L17-L20
    .long   L18-L20
    .long   L19-L20
    L19:
    [...]
    
    Run Code Online (Sandbox Code Playgroud)

    问题在于 cmp $7, %eax; ja L11;

  3. 好的,我将使用丑陋的解决方案并为4.4以下的gcc版本添加一个特殊情况,使用不带开关的不同版本并使用goto和gcc的&&标签扩展.

    static void *jtb[] = { &&c_1, &&c_2, &&c_3, &&c_4, &&c_5, &&c_6, &&c_7, &&c_8 };
    [...]
    goto *jtb[a & 0x7];
    [...]
    while(0) {
    c_1:
    // something
    break;
    c_2:
    // something
    break;
    [...]
    }
    
    Run Code Online (Sandbox Code Playgroud)

    请注意,标签数组是静态的,因此不会在每次调用时计算.

Pau*_*l R 5

也许您可以使用函数指针数组而不是开关?

#include <stdio.h>

typedef void (*func)(void);

static void f0(void) { printf("%s\n", __FUNCTION__); }
static void f1(void) { printf("%s\n", __FUNCTION__); }
static void f2(void) { printf("%s\n", __FUNCTION__); }
static void f3(void) { printf("%s\n", __FUNCTION__); }
static void f4(void) { printf("%s\n", __FUNCTION__); }
static void f5(void) { printf("%s\n", __FUNCTION__); }
static void f6(void) { printf("%s\n", __FUNCTION__); }
static void f7(void) { printf("%s\n", __FUNCTION__); }

int main(void)
{
    const func f[8] = { f0, f1, f2, f3, f4, f5, f6, f7 };
    int i;

    for (i = 0; i < 8; ++i)
    {
        f[i]();
    }
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

  • 很酷,但这不会增加函数调用开销吗?但是,是的,你回答这个问题,因为它没有分支.(哎呀,现在有4个有效[答案],不知道[哪一个] [标记为已接受]编辑:我试图强行内联,没有运气.替代方案是goto并使用gcc的&&标签(我昨天尝试过的但未提及,因为我不想在代码中强制使用GCC依赖项.) (2认同)
  • (抱歉死了……)只要您进行真正的函数调用,函数调用开销就是给定的。如果您通过让 fN 返回 int 将其转换为尾调用,则应该消除这种情况。 (2认同)

nmi*_*els 1

我尝试编译一些简单且与 -O5 和 -fno-inline 相当的东西(我的 f0-f7 函数很简单),它生成了以下内容:


 8048420:   55                      push   %ebp ;; function preamble
 8048421:   89 e5                   mov    %esp,%ebp ;; Yeah, yeah, it's a function.
 8048423:   83 ec 04                sub    $0x4,%esp ;; do stuff with the stack
 8048426:   8b 45 08                mov    0x8(%ebp),%eax ;; x86 sucks, we get it
 8048429:   83 e0 07                and    $0x7,%eax ;; Do the (a & 0x7)
 804842c:   ff 24 85 a0 85 04 08    jmp    *0x80485a0(,%eax,4) ;; Jump table!
 8048433:   90                      nop
 8048434:   8d 74 26 00             lea    0x0(%esi,%eiz,1),%esi
 8048438:   8d 45 08                lea    0x8(%ebp),%eax
 804843b:   89 04 24                mov    %eax,(%esp)
 804843e:   e8 bd ff ff ff          call   8048400 
 8048443:   8b 45 08                mov    0x8(%ebp),%eax
 8048446:   c9                      leave  
Run Code Online (Sandbox Code Playgroud)

您尝试过优化级别吗?

  • 没有-O5(http://gcc.gnu.org/onlinedocs/gcc-4.4.4/gcc/Optimize-Options.html),所以它与-O3的作用相同并不奇怪;-) (6认同)