第一种情况(通过switch()
)为我创建以下(x86_64的Linux的/ GCC 4.4):
400570: ff 24 c5 b8 06 40 00 jmpq *0x4006b8(,%rax,8)
[ ... ]
400580: 31 c0 xor %eax,%eax
400582: e8 e1 fe ff ff callq 400468 <[email protected]>
400587: 31 c0 xor %eax,%eax
400589: 48 83 c4 08 add $0x8,%rsp
40058d: c3 retq
40058e: bf a4 06 40 00 mov $0x4006a4,%edi
400593: eb eb jmp 400580 <main+0x30>
400595: bf a9 06 40 00 mov $0x4006a9,%edi
40059a: eb e4 jmp 400580 <main+0x30>
40059c: bf ad 06 40 00 mov $0x4006ad,%edi
4005a1: eb dd jmp 400580 <main+0x30>
4005a3: bf b1 06 40 00 mov $0x4006b1,%edi
4005a8: eb d6 jmp 400580 <main+0x30>
[ ... ]
Contents of section .rodata:
[ ... ]
4006b8 8e054000 p ... ]
注意.rodata
内容@4006b8
打印网络字节顺序(无论出于何种原因...) ,值为40058e
,它在上面的main
之内 - 其中arg初始值设定程序/ jmp
块启动。所有在那里使用8个字节的mov
/jmp
对,因此间接使用(,%rax,8)
。在这种情况下,该序列因此:
jmp <to location that sets arg for printf()>
...
jmp <back to common location for the printf() invocation>
...
call <printf>
...
retq
这意味着编译器实际上已经优化了的static
调用点 - 而是合并他们都到一个单一的,内联printf()
电话。这里使用的表格是jmp ...(,%rax,8)
指令,表格中包含程序代码内的。
第二个(用明确创建的表),并为我以下:
0000000000400550 <print0>:
[ ... ]
0000000000400560 <print1>:
[ ... ]
0000000000400570 <print2>:
[ ... ]
0000000000400580 <print3>:
[ ... ]
0000000000400590 <print4>:
[ ... ]
00000000004005a0 <main>:
4005a0: 48 83 ec 08 sub $0x8,%rsp
4005a4: bf d4 06 40 00 mov $0x4006d4,%edi
4005a9: 31 c0 xor %eax,%eax
4005ab: 48 8d 74 24 04 lea 0x4(%rsp),%rsi
4005b0: e8 c3 fe ff ff callq 400478 <[email protected]>
4005b5: 8b 54 24 04 mov 0x4(%rsp),%edx
4005b9: 31 c0 xor %eax,%eax
4005bb: ff 14 d5 60 0a 50 00 callq *0x500a60(,%rdx,8)
4005c2: 31 c0 xor %eax,%eax
4005c4: 48 83 c4 08 add $0x8,%rsp
4005c8: c3 retq
[ ... ]
500a60 50054000 00000000 60054000 00000000 [email protected]`[email protected]
500a70 70054000 00000000 80054000 00000000 [email protected]@.....
500a80 90054000 00000000 [email protected]
同样要注意倒字节顺序objdump的打印数据部分 - 如果你把这些你身边得到功能地址为print[0-4]()
。
编译器通过间接call
调用目标 - 即表的使用是直接在call
指令,并且该表已经_explicitly被创建为数据。
编辑:
如果改变这样的源:
#include <stdio.h>
static inline void print0() { printf("Zero"); }
static inline void print1() { printf("One"); }
static inline void print2() { printf("Two"); }
static inline void print3() { printf("Three"); }
static inline void print4() { printf("Four"); }
void main(int argc, char **argv)
{
static void (*jt[])() = { print0, print1, print2, print3, print4 };
return jt[argc]();
}
创建大会main()
变为:
0000000000400550 <main>:
400550: 48 63 ff movslq %edi,%rdi
400553: 31 c0 xor %eax,%eax
400555: 4c 8b 1c fd e0 09 50 mov 0x5009e0(,%rdi,8),%r11
40055c: 00
40055d: 41 ff e3 jmpq *%r11d
这看起来更像是你想要的吗?
原因是你需要“无堆栈”funcs才能做到这一点 - tail-recursion(通过jmp
而不是ret
从函数返回)只有在你已经完成所有堆栈清理的情况下才有可能,或者不必做任何事情,因为你没有任何东西需要清理。编译器可以(但不需要)在最后一次函数调用之前选择清除(在这种情况下,最后一次调用可以由jmp
进行),但只有当您返回从该函数获得的值时,或者如果您“return void
”。并且,如上所述,如果你实际上使用使用堆栈(就像你的例子为input
变量所做的那样),没有任何东西可以让编译器强制以这种方式解除尾递归结果。
EDIT2:
拆卸的第一个例子,用同样的变化(中input
并迫使void main
argc
代替 - 没有标准的一致性意见,请这是一个演示),结果如下组件:
0000000000400500 <main>:
400500: 83 ff 04 cmp $0x4,%edi
400503: 77 0b ja 400510 <main+0x10>
400505: 89 f8 mov %edi,%eax
400507: ff 24 c5 58 06 40 00 jmpq *0x400658(,%rax,8)
40050e: 66 data16
40050f: 90 nop
400510: f3 c3 repz retq
400512: bf 3c 06 40 00 mov $0x40063c,%edi
400517: 31 c0 xor %eax,%eax
400519: e9 0a ff ff ff jmpq 400428 <[email protected]>
40051e: bf 41 06 40 00 mov $0x400641,%edi
400523: 31 c0 xor %eax,%eax
400525: e9 fe fe ff ff jmpq 400428 <[email protected]>
40052a: bf 46 06 40 00 mov $0x400646,%edi
40052f: 31 c0 xor %eax,%eax
400531: e9 f2 fe ff ff jmpq 400428 <[email protected]>
400536: bf 4a 06 40 00 mov $0x40064a,%edi
40053b: 31 c0 xor %eax,%eax
40053d: e9 e6 fe ff ff jmpq 400428 <[email protected]>
400542: bf 4e 06 40 00 mov $0x40064e,%edi
400547: 31 c0 xor %eax,%eax
400549: e9 da fe ff ff jmpq 400428 <[email protected]>
40054e: 90 nop
40054f: 90 nop
这是一个糟糕的方式(不 jmp
而不是一个),但在另一个(更好,因为它消除了static
功能和内联代码)。在优化方面,编译器几乎完成了同样的事情。
是否有性能差异?而且我不相信第二种情况下的呼叫是内联的。你可以发布程序集吗? – Mysticial
也许是因为在后一种情况下,函数间接调用?如果将jt [] 5个常量指针的常量数组设置为函数,会发生什么? –
@Alex - 你打败了我!非const指针数组可以在运行时修改。 –