Código de nivel de ensamblaje de la caja del interruptor

Código de nivel de ensamblaje de la caja del interruptor


Estoy programando C en cygwin windows. Después de haber hecho un poco de programación en C y sentirme cómodo con el lenguaje, quería mirar debajo del capó y ver qué está haciendo el compilador para el código que escribo.


Así que escribí un bloque de código que contenía declaraciones de cambio de caso y las convertí en ensamblaje usando:


gcc -S foo.c  

Aquí está la fuente 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;
}
}

Ahora el conjunto resultante para el mismo es:


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:

Ahora, en el ensamblaje, el código verifica primero el último caso (es decir, el caso 10). Esto es muy extraño. Y luego está copiando 'i' en 'eax' y haciendo cosas que están más allá de mí.


Escuché que el compilador implementa alguna tabla de saltos para switch..case. ¿Es lo que está haciendo este código? ¿O qué está haciendo y por qué? Porque en el caso de una menor cantidad de casos,
el código es bastante similar al generado para la escalera if...else, pero cuando aumenta la cantidad de casos, se ve esta implementación de aspecto inusual.


Gracias de antemano.


Respuestas:


Primero, el código compara i con 10 y salta al caso predeterminado cuando el valor es mayor que 10 (cmpl $10, -4(%ebp) seguido de ja L13 ).


El siguiente bit de código está desplazando la entrada a la izquierda en dos (sall $2, %eax ) que es lo mismo que múltiplo por cuatro, lo que genera un desplazamiento en la tabla de saltos (porque cada entrada en la tabla tiene una longitud de 4 bytes)


Luego carga una dirección desde la tabla de salto (movl L14(%eax), %eax ) y salta a él (jmp *%eax ).


La tabla de saltos es simplemente una lista de direcciones (representadas en código ensamblador por etiquetas):


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

Una cosa a tener en cuenta es que L13 representa el caso predeterminado. Es a la vez la primera entrada en la tabla de saltos (para cuando i es 0) y se maneja especialmente al principio (cuando i> 10).