switch语句使用一种跳转表(jump table)的结构来实现程序的转移。在某些情况下可以得到比if~else高效的跳转效果。
跳转表是一个数组,表项i是一个代码段的地址。程序代码用switch索引值来执行一个跳转表内的数据引用,确认跳转指令的目的。
首先我看了这个网页 http://www.cublog.cn/u1/46723/showart_1272331.html “switch与跳转表(jump table)” 讲了一些跳转表的扫盲知识。
这篇文章中作者说源代码中case语句比较少时,switch会使用cmpl与jmp组合的跳转方式,类似于if~else的方式。当case语句较多时(大于4个),就会使用跳转表。
我经过实验,发现并不完全是这样。
首先,有些编译器会忽略空case,比如GCC。
比如源程序如下
int switch_test_first(int x) { long int res; switch(x) { case 100: res = 1; break; case 102: res = 2; break; case 104: res = 3; break; case 106: case 108: case 110: case 112: case 114: case 116: break; } return res; }
编译出来的汇编码为:(GCC)
.file "da.c" .text .globl switch_test_first .type switch_test_first, @function switch_test_first: .LFB2: pushq %rbp .LCFI0: movq %rsp, %rbp .LCFI1: movl %edi, -20(%rbp) movl -20(%rbp), %eax movl %eax, -24(%rbp) cmpl $102, -24(%rbp) je .L4 cmpl $104, -24(%rbp) je .L5 cmpl $100, -24(%rbp) je .L3 jmp .L2 .L3: movq $1, -8(%rbp) jmp .L2 .L4: movq $2, -8(%rbp) jmp .L2 .L5: movq $3, -8(%rbp) .L2: movq -8(%rbp), %rax leave ret
而ICC仍然会将这些空case放出来。。。。。
# -- Machine type EFI2 # mark_description "Intel(R) C++ Compiler Professional for applications running on Intel(R) 64, Version 11.0 Build 20090131 %"; # mark_description "s"; # mark_description "-S"; .file "da.c" .text ..TXTST0: # -- Begin switch_test_first # mark_begin; .align 16,0x90 .globl switch_test_first switch_test_first: # parameter 1: %edi ..B1.1: # Preds ..B1.0 ..___tag_value_switch_test_first.1: #2.1 addl $-100, %edi #4.2 cmpl $16, %edi #4.2 ja ..B1.23 # Prob 50% #4.2 # LOE rax rbx rbp r12 r13 r14 r15 edi ..B1.2: # Preds ..B1.1 movl %edi, %edi #4.2 movq ..1..TPKT.1_0.0.1(,%rdi,8), %rax #4.2 jmp *%rax #4.2 # LOE rbx rbp r12 r13 r14 r15 ..1.1_0.TAG.074.0.1: ..1.1_0.TAG.072.0.1: ..1.1_0.TAG.070.0.1: ..1.1_0.TAG.06e.0.1: ..1.1_0.TAG.06c.0.1: ..1.1_0.TAG.06a.0.1: ..1.1_0.TAG.DEFAULT.0.1: ..B1.4: # Preds ..B1.2 ..B1.2 ..B1.2 ..B1.2 ..B1.2 # ..B1.2 ..B1.2 jmp ..B1.23 # Prob 100% #23.9 # LOE rax rbx rbp r12 r13 r14 r15 ..1.1_0.TAG.068.0.1: ..B1.16: # Preds ..B1.2 movl $3, %eax #13.4 jmp ..B1.23 # Prob 100% #13.4 # LOE rax rbx rbp r12 r13 r14 r15 ..1.1_0.TAG.066.0.1: ..B1.18: # Preds ..B1.2 movl $2, %eax #10.4 jmp ..B1.23 # Prob 100% #10.4 # LOE rax rbx rbp r12 r13 r14 r15 ..1.1_0.TAG.064.0.1: ..B1.22: # Preds ..B1.2 movl $1, %eax #7.4 # LOE rax rbx rbp r12 r13 r14 r15 ..B1.23: # Preds ..B1.4 ..B1.16 ..B1.18 ..B1.22 ..B1.1 # ret #23.9 .align 16,0x90 ..___tag_value_switch_test_first.2: # # LOE # mark_end; .type switch_test_first,@function .size switch_test_first,.-switch_test_first .section .rodata, "a" .align 32 .align 32 ..1..TPKT.1_0.0.1: .quad ..1.1_0.TAG.064.0.1 .quad ..1.1_0.TAG.DEFAULT.0.1 .quad ..1.1_0.TAG.066.0.1 .quad ..1.1_0.TAG.DEFAULT.0.1 .quad ..1.1_0.TAG.068.0.1 .quad ..1.1_0.TAG.DEFAULT.0.1 .quad ..1.1_0.TAG.06a.0.1 .quad ..1.1_0.TAG.DEFAULT.0.1 .quad ..1.1_0.TAG.06c.0.1 .quad ..1.1_0.TAG.DEFAULT.0.1 .quad ..1.1_0.TAG.06e.0.1 .quad ..1.1_0.TAG.DEFAULT.0.1 .quad ..1.1_0.TAG.070.0.1 .quad ..1.1_0.TAG.DEFAULT.0.1 .quad ..1.1_0.TAG.072.0.1 .quad ..1.1_0.TAG.DEFAULT.0.1 .quad ..1.1_0.TAG.074.0.1 .data # -- End switch_test_first
看那一串quad的地方,ICC好像没有这么智能哦。。。
另外一个问题,就是跳转表使用索引来实现跳转的一个前提就是:case的数一定要有规律!!!!
就是说这几个数最好差的不太多,最好是等差数列。比如100,102,104,106,108,110这样的。如果出来一堆随机的数,那编译器也会傻眼的。
比如下面这个例子。
int switch_test_first(int x) { long int res; switch(x) { case 1100: res = 1; break; case 102: res = 2; break; case 4603: res = 3; break; case 11044: res = 1; break; case 1505: res = 2; break; case 8106: res = 3; break; case 14017: res = 1; break; case 1908: res = 2; break; case 9: res = 3; break; } return res; }
这种情况下,编译器就不会使用跳转表来实现跳转了。
有趣的是,gcc和icc对这种情况的处理也都不同
先看GCC的:
.file "ta.c" .text .globl switch_test_first .type switch_test_first, @function switch_test_first: .LFB2: pushq %rbp .LCFI0: movq %rsp, %rbp .LCFI1: movl %edi, -20(%rbp) movl -20(%rbp), %eax movl %eax, -24(%rbp) cmpl $1908, -24(%rbp) je .L7 cmpl $1908, -24(%rbp) jg .L12 cmpl $102, -24(%rbp) je .L4 cmpl $102, -24(%rbp) jg .L13 cmpl $9, -24(%rbp) je .L3 jmp .L2 .L13: cmpl $1100, -24(%rbp) je .L5 cmpl $1505, -24(%rbp) je .L6 jmp .L2 .L12: cmpl $8106, -24(%rbp) je .L9 cmpl $8106, -24(%rbp) jg .L14 cmpl $4603, -24(%rbp) je .L8 jmp .L2 .L14: cmpl $11044, -24(%rbp) je .L10 cmpl $14017, -24(%rbp) je .L11 jmp .L2 .L5: movq $1, -8(%rbp) jmp .L2 .L4: movq $2, -8(%rbp) jmp .L2 .L8: movq $3, -8(%rbp) jmp .L2 .L10: movq $1, -8(%rbp) jmp .L2 .L6: movq $2, -8(%rbp) jmp .L2 .L9: movq $3, -8(%rbp) jmp .L2 .L11: movq $1, -8(%rbp) jmp .L2 .L7: movq $2, -8(%rbp) jmp .L2 .L3: movq $3, -8(%rbp) .L2: movq -8(%rbp), %rax leave ret
GCC使用类似于if~else的跳转方式。
看看ICC怎么办:
# -- Machine type EFI2 # mark_description "Intel(R) C++ Compiler Professional for applications running on Intel(R) 64, Version 11.0 Build 20090131 %"; # mark_description "s"; # mark_description "-S"; .file "da.c" .text ..TXTST0: # -- Begin switch_test_first # mark_begin; .align 16,0x90 .globl switch_test_first switch_test_first: # parameter 1: %edi ..B1.1: # Preds ..B1.0 ..___tag_value_switch_test_first.1: #2.1 cmpl $1100, %edi #4.9 je ..B1.11 # Prob 10% #4.9 # LOE rbx rbp r12 r13 r14 r15 edi ..B1.2: # Preds ..B1.1 cmpl $102, %edi #4.9 je ..B1.9 # Prob 11% #4.9 # LOE rbx rbp r12 r13 r14 r15 edi ..B1.3: # Preds ..B1.2 cmpl $4603, %edi #4.9 je ..B1.13 # Prob 12% #4.9 # LOE rbx rbp r12 r13 r14 r15 edi ..B1.4: # Preds ..B1.3 cmpl $11044, %edi #4.9 je ..B1.11 # Prob 14% #4.9 # LOE rbx rbp r12 r13 r14 r15 edi ..B1.5: # Preds ..B1.4 cmpl $1505, %edi #4.9 je ..B1.9 # Prob 16% #4.9 # LOE rbx rbp r12 r13 r14 r15 edi ..B1.6: # Preds ..B1.5 cmpl $8106, %edi #4.9 je ..B1.13 # Prob 20% #4.9 # LOE rbx rbp r12 r13 r14 r15 edi ..B1.7: # Preds ..B1.6 cmpl $14017, %edi #4.9 je ..B1.11 # Prob 25% #4.9 # LOE rbx rbp r12 r13 r14 r15 edi ..B1.8: # Preds ..B1.7 cmpl $1908, %edi #4.9 jne ..B1.10 # Prob 67% #4.9 # LOE rbx rbp r12 r13 r14 r15 edi ..B1.9: # Preds ..B1.2 ..B1.5 ..B1.8 movl $2, %edx #28.4 jmp ..B1.12 # Prob 100% #28.4 ..B1.10: # Preds ..B1.8 movl $3, %eax #31.4 xorl %edx, %edx #31.4 cmpl $9, %edi #31.4 cmove %rax, %rdx #31.4 jmp ..B1.12 # Prob 100% #31.4 # LOE rdx rbx rbp r12 r13 r14 r15 ..B1.11: # Preds ..B1.1 ..B1.4 ..B1.7 movl $1, %edx #25.4 # LOE rdx rbx rbp r12 r13 r14 r15 ..B1.12: # Preds ..B1.13 ..B1.11 ..B1.9 ..B1.10 movl %edx, %eax #35.9 ret #35.9 # LOE ..B1.13: # Preds ..B1.3 ..B1.6 # Infreq movl $3, %edx #22.4 jmp ..B1.12 # Prob 100% #22.4 .align 16,0x90 ..___tag_value_switch_test_first.2: # # LOE rdx rbx rbp r12 r13 r14 r15 # mark_end; .type switch_test_first,@function .size switch_test_first,.-switch_test_first .data # -- End switch_test_first
哥们很霸气啊,直接原封不动抄下来了。。。。。
不知道这两种方式谁的效率更高一些,等有时间了我写个程序测试一下。