Код уровня сборки корпуса переключателя - PullRequest
14 голосов
/ 10 июня 2010

Я программирую C на окнах Cygwin.Пройдя немного программирования на C и освоившись с языком, я хотел заглянуть под капот и посмотреть, что делает компилятор для кода, который я пишу.

Итак, я записал блок кода, содержащий операторы регистра переключателей, и преобразовал их в сборку, используя:

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 ]

23 голосов
/ 10 июня 2010

Сначала код сравнивает i с 10 и переходит к случаю по умолчанию, когда значение больше 10 (cmpl $10, -4(%ebp), за которым следует ja L13).

Следующий бит кода смещает ввод влево на два (sall $2, %eax), что аналогично умножению на четыре, что создает смещение в таблице переходов (поскольку каждая запись в таблице имеет длину 4 байта)

Затем он загружает адрес из таблицы переходов (movl L14(%eax), %eax) и переходит к нему (jmp *%eax).

Таблица переходов - это просто список адресов (представленных в коде сборки метками):

L14:
.long   L13
.long   L3
.long   L4
...

Следует отметить, что L13 представляет регистр по умолчанию. Это первая запись в таблице переходов (для случая, когда i равно 0), и она обрабатывается специально в начале (когда i> 10).

3 голосов
/ 10 июня 2010

Да, это таблица прыжков. Первая проверка состоит в том, чтобы проверить, есть ли значение в случаях, и перейти к значению по умолчанию, если это не так. Не забывайте, что в такой таблице, если% eax равно 0, L14 (% eax) указывает на первый элемент таблицы (L13). Таким образом, в таблице case 10: проиндексирован с 9, а не 10.

Способ переключения зависит от значений, которые вы указали в case; в этом случае они находятся в «последовательности», поэтому возможна простая таблица переходов.

2 голосов
/ 10 июня 2010

Для [1..10] компилятор сгенерирует таблицу, так что ему не нужно сравнивать значение, чтобы куда-то идти, он напрямую делает: goto table[i]. Так быстрее.

Но в случае i > 10 он переходит к вашей инструкции по умолчанию. Прежде чем прыгать, необходимо сначала проверить, что программа с треском провалилась.

Если бы у вас были разреженные значения (например, 23, 9233, 91238, а не 1, 2, 3 ...), компилятор не сгенерировал бы такую ​​таблицу и сравнил бы каждое значение.

0 голосов
/ 10 июня 2010

Да, первый eax рассчитывается по значению переключателя (sall сдвиг как умножение), чтобы получить адрес из таблицы переходов (после метки L14:)

jmp *%eax - это быстрый переход к ярлыку вашего дела. (JMP около Eax)

Код, следующий за другими этикетками, просто печатается и пропускает другие случаи.

...