切换机箱组件级别代码
我在cygwin windows上编写C语言。 在完成了一些C编程并熟悉语言后,我想深入了解编译器为我编写的代码做了些什么。
所以我写下了一个包含switch case语句的代码块,并使用以下方法将它们转换为汇编:
gcc -S foo.c
这是C源:
switch(i) { case 1: { printf("Case 1\n"); break; } case 2: { printf("Case 2\n"); break; } case 3: { printf("Case 3\n"); break; } case 4: { printf("Case 4\n"); break; } case 5: { printf("Case 5\n"); break; } case 6: { printf("Case 6\n"); break; } case 7: { printf("Case 7\n"); break; } case 8: { printf("Case 8\n"); break; } case 9: { printf("Case 9\n"); break; } case 10: { printf("Case 10\n"); break; } default: { printf("Nothing\n"); break; } }
现在,相同的组件是:
movl $5, -4(%ebp) cmpl $10, -4(%ebp) ja L13 movl -4(%ebp), %eax sall $2, %eax movl L14(%eax), %eax jmp *%eax .section .rdata,"dr" .align 4 L14: .long L13 .long L3 .long L4 .long L5 .long L6 .long L7 .long L8 .long L9 .long L10 .long L11 .long L12 .text L3: movl $LC0, (%esp) call _printf jmp L2 L4: movl $LC1, (%esp) call _printf jmp L2 L5: movl $LC2, (%esp) call _printf jmp L2 L6: movl $LC3, (%esp) call _printf jmp L2 L7: movl $LC4, (%esp) call _printf jmp L2 L8: movl $LC5, (%esp) call _printf jmp L2 L9: movl $LC6, (%esp) call _printf jmp L2 L10: movl $LC7, (%esp) call _printf jmp L2 L11: movl $LC8, (%esp) call _printf jmp L2 L12: movl $LC9, (%esp) call _printf jmp L2 L13: movl $LC10, (%esp) call _printf L2:
现在,在程序集中,代码首先检查最后一个案例(即案例10)。 这很奇怪。 然后它将’我’复制到’eax’并做一些超出我的事情。
我听说编译器为switch..case实现了一些跳转表。 这个代码在做什么? 或者它在做什么以及为什么? 因为在案例数较少的情况下,代码与if … else梯形图的代码非常相似,但是当案例数增加时,可以看到这种看似不寻常的实现。
提前致谢。
首先,代码将i与10进行比较,并在值大于10时跳转到默认情况( cmpl $10, -4(%ebp)
后跟ja L13
)。
下一位代码将输入向左移动两个( sall $2, %eax
),这与多个乘以4相同,后者在跳转表中生成一个偏移量(因为表中的每个条目长度为4个字节)
然后它从跳转表中加载一个地址( movl L14(%eax), %eax
)并跳转到它( jmp *%eax
)。
跳转表只是一个地址列表(按标签用汇编代码表示):
L14: .long L13 .long L3 .long L4 ...
需要注意的一点是, L13
代表默认情况。 它既是跳转表中的第一个条目(当我为0时)并且在开始时特别处理(当i> 10时)。
是的,它是一个跳转表。 第一个检查是检查值是否在案例中,如果不是,则跳转到默认值。 不要忘记在这样的表中,如果%eax为0,则L14(%eax)指向表的第一个元素(L13)。 所以在表格中, case 10:
用9而不是10来索引。
切换的方式取决于你的case
; 在这种情况下,它们处于“序列”中,因此简单的跳转表是可能的。
对于[ 1..10
],编译器将生成一个表,这样它就不需要将值比较到某处,它直接执行: goto table[i]
。 这样它更快。
但是如果i > 10
它会跳转到您的默认语句。 它必须在跳跃之前先检查,否则程序会崩溃。
如果您有稀疏值(例如,23,9233,91238,而不是1,2,3 ……),编译器将不会生成这样的表,并比较每个值。
是的,第一个eax是通过开关值( sall
shift as multiplication)来计算的,以从跳转表中获取地址(在标签L14:
)
jmp *%eax
是一个接近你的案件标签的跳转。 (靠近eax的jmp)
其他标签后面的代码只是打印并跳过其他情况。