这只是 4.4 之前的 GCC 版本的问题,在 GCC 4.5 中已修复。
是否可以告诉编译器 switch 中使用的变量适合提供的 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;
}
}
我尝试 xor'ing 到低位(例如),使用枚举,使用 gcc_unreachable() 无济于事。生成的代码总是检查变量是否在范围内,添加一个无意义的分支条件并移走跳转表计算代码。
注意:这是在解码器的最内层循环中,性能很重要。
没有办法告诉 gcc 永远不会采用默认分支,尽管如果它可以根据早期的条件检查证明该值从未超出范围,它将省略默认分支。
那么,你如何帮助 gcc 证明变量适合并且在上面的示例中没有默认分支?(当然,不添加条件分支。)
更新
这是在带有 GCC 4.2 的 OS X 10.6 Snow Leopard 上(默认来自 Xcode。)它没有发生在 linux 中的 GCC 4.4/4.3 上(由 Nathon 和 Jens Gustedt 报告。)
示例中的函数是为了便于阅读,认为它们是内联的或只是语句。在 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: [...]
问题在于
cmp $7, %eax;
ja L11;
好的,我将使用丑陋的解决方案,并为 4.4 以下的 gcc 版本添加一个特殊情况,使用不带开关的不同版本并使用 goto 和 gcc 的 &&label 扩展。
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; [...] }
请注意,标签数组是静态的,因此不会在每次调用时都计算它。