Schaltgehäuse-Baugruppenebenencode

Schaltgehäuse-Baugruppenebenencode


Ich programmiere C auf Cygwin Windows. Nachdem ich ein wenig C-Programmierung gemacht und mich mit der Sprache vertraut gemacht hatte, wollte ich unter die Haube schauen und sehen, was der Compiler für den von mir geschriebenen Code tut.


Also habe ich einen Codeblock aufgeschrieben, der switch case-Anweisungen enthält, und sie mit

in Assembler konvertiert
gcc -S foo.c  

Hier ist die C-Quelle:


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;
}
}

Nun ist die resultierende Assembly für dasselbe:


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:

Jetzt überprüft der Code in der Assembly zuerst den letzten Fall (dh Fall 10). Das ist sehr seltsam. Und dann kopiert es 'i' in 'eax' und tut Dinge, die mir ein Rätsel sind.


Ich habe gehört, dass der Compiler eine Sprungtabelle für switch..case implementiert. Ist es das, was dieser Code tut? Oder was macht es und warum? Denn bei einer geringeren Anzahl von Fällen
ist der Code ziemlich ähnlich zu dem für die if...else-Leiter generierten, aber wenn die Anzahl der Fälle zunimmt, wird diese ungewöhnlich aussehende Implementierung sichtbar.


Vielen Dank im Voraus.


Antworten:


Zuerst vergleicht der Code das i mit 10 und springt zum Standardfall, wenn der Wert größer als 10 ist (cmpl $10, -4(%ebp) gefolgt von ja L13 ).


Das nächste Codebit verschiebt die Eingabe um zwei nach links (sall $2, %eax ), was dasselbe ist wie ein Vielfaches von vier, das einen Offset in die Sprungtabelle erzeugt (weil jeder Eintrag in der Tabelle 4 Bytes lang ist)


Anschließend lädt er eine Adresse aus der Sprungtabelle (movl L14(%eax), %eax ) und springt dorthin (jmp *%eax ).


Die Sprungtabelle ist einfach eine Liste von Adressen (dargestellt im Assemblercode durch Labels):


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

Zu beachten ist, dass L13 stellt den Standardfall dar. Es ist sowohl der erste Eintrag in der Sprungtabelle (wenn i 0 ist) als auch am Anfang besonders behandelt (wenn i> 10).