Код уровня сборки коробки передач
Я программирую C на окнах cygwin. Проделав немного программирования на C и устроившись на этом языке, я хотел посмотреть под капотом и посмотреть, что делает компилятор для кода, который я пишу.
Итак, я записал блок кода, содержащий инструкции оператора switch, и преобразовал их в сборку, используя:
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). Это очень странно. И затем он копирует "i" в "eax" и делает вещи, которые находятся вне меня.
Я слышал, что компилятор реализует некоторую таблицу перехода для switch..case. Это то, что делает этот код? Или что он делает и почему? Потому что в случае меньшего количества случаев,
код очень похож на тот, который сгенерирован для if... else ladder, но когда число случаев увеличивается, эта необычно выглядящая реализация видна.
Спасибо заранее.
Ответы
Ответ 1
Сначала код сравнивает я с 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
представляет собой случай по умолчанию. Это первая запись в таблице переходов (когда я равно 0) и обрабатывается специально в начале (при i > 10).
Ответ 2
Да, это таблица перехода. Первая проверка заключается в том, чтобы проверить, соответствует ли значение в случаях, и переходить к умолчанию, если это не так. Не забывайте, что в такой таблице, если% eax равно 0, L14 (% eax) указывает на первый элемент таблицы (L13). Поэтому в таблице case 10:
индексируется с 9, а не 10.
Способ, которым может выполняться переключатель, зависит от значений, которые у вас есть в case
; в этом случае они находятся в "последовательности", поэтому возможна простая таблица перехода.
Ответ 3
Для [1..10
] компилятор сгенерирует таблицу, так что ей не нужно сравнивать значение, чтобы идти куда-то, он непосредственно выполняет: goto table[i]
. Таким образом, это быстрее.
Но в случае i > 10
он переходит к вашему заявлению по умолчанию. Он должен проверить сначала, прежде чем прыгать в противном случае, программа будет несчастно сбой.
Если у вас были разреженные значения (например, 23, 9233, 91238, а не 1, 2, 3...), компилятор не создавал бы такую таблицу и не сравнивал бы каждое значение.
Ответ 4
Да, первый eax вычисляется значением переключателя (sall
shift как умножение), чтобы получить адрес из таблицы перехода (следующая метка L14:
)
jmp *%eax
- это приближенный переход к метке вашего случая. (jmp около eax)
Код, следующий за другими ярлыками, просто печатает и пропускает другие случаи.