第一種情況(通過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指針數組可以在運行時修改。 –