切换机箱组件级别代码

时间:2010-06-10 06:08:02

标签: c gcc assembly cygwin

我在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梯形图,但是当案例数量增加时,可以看到这种看似不寻常的实现。

提前致谢。

4 个答案:

答案 0 :(得分:23)

首先,代码将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时)。

答案 1 :(得分:3)

是的,它是一个跳转表。第一个检查是检查值是否在案例中,如果不是则跳转到默认值。不要忘记在这样的表中,如果%eax为0,则L14(%eax)指向表的第一个元素(L13)。因此,在表格中,case 10:的索引为9,而不是10。

切换的方式取决于case中的值;在这种情况下,它们处于“序列”中,因此可以使用简单的跳转表。

答案 2 :(得分:2)

对于[1..10],编译器将生成一个表,这样它就不需要将值比较到某处,它直接执行:goto table[i]。这样它会更快。

但是在i > 10的情况下,它会跳转到您的默认语句。它必须在跳跃之前先检查,否则程序会崩溃。

如果您有稀疏值(例如,23,9233,91238,而不是1,2,3 ......),编译器将不会生成这样的表,并比较每个值。

答案 3 :(得分:0)

是的,第一个eax是通过切换值(sall移位作为乘法)来计算的,以从跳转表中获取地址(在标签L14:之后)

jmp *%eax几乎是你案件标签的跳转。         (jmp在eax附近)

其他标签后面的代码只是打印并跳过其他情况。