Schleife nachvollziehen
-
Hallo, ich hab ein kleines C-Programm, was so aussieht:
char s[] = {'A','B','C', ...}; int main() { printf("Hallo"); int i; for(i = 0; s[i]; ++i) printf("%d\n",s[i]); printf("%s\n",s); return 0; }
Nun bekomme ich vom GCC 4.7 unter Ubuntu dazu folgenden Assembler-Code:
main: .LFB0: pushq %rbp movq %rsp, %rbp subq $16, %rsp movl $.LC0, %edi # Ausgabe von "Hallo" call puts movl $0, -4(%rbp) # Hier wird i auf 0 gesetzt jmp .L2 .L3: movl -4(%rbp), %eax cltq movzbl s(%rax), %eax movsbl %al, %eax movl %eax, %esi movl $.LC1, %edi movl $0, %eax call printf # Ausgabe der Array-Elemente addl $1, -4(%rbp) # ++i .L2: movl -4(%rbp), %eax # i wird nach %eax geschoben cltq # hmmmmm ....? movzbl s(%rax), %eax # %eax = %rax - s .... oder? testb %al, %al # hmmmmm ....? jne .L3 movl $s, %esi movl $.LC2, %edi movl $0, %eax call printf # Ausgabe des kompletten Arrays movl $0, %eax leave ret
Auch wenn ich im Internet schon einige ähnliche Fragen gefunden habe, so leuchtet mir immer noch nicht komplett ein, wie hier die Schleife realisiert wird. Der bedingte Sprung zu .L3 wird ja genau dann ausgeführt, wenn ZF = 0, bzw. %al != %al, was ja nicht vorkommen kann. Aber i wird ja immer nur hochgezählt, wie genau wird hier die Schleife abgebrochen?
Danke für eure Hilfe schon einmal ...
-
char s[] = {'A','B','C', ...}; int main() { printf("Hallo"); int i; for(i = 0; s[i]; ++i) printf("%d\n",s[i]); printf("%s\n",s); return 0; }
main: # Setze Funktions-Label .LFB0: # Setze Label zu Funktionsbeginn pushq %rbp # Sichere den Frame Pointer auf dem Stack movq %rsp, %rbp # Kopiere den Stack Pointer zum Frame Pointer subq $16, %rsp # Reserviere Platz auf dem Stack in der Größe von 16 Quad-Words (=16*8 Byte) movl $.LC0, %edi # Kopiere den Pointer zum konstanten String "Hallo" in Register edi call puts # Sende den String Pointer in edi zum Ausgabe-Stream movl $0, -4(%rbp) # Nutze den reservierten Stack Raum für long Variable i und setze ihren Wert auf 0 jmp .L2 # Springe zu Schleifen-Kopf / prüfe Schleifen-Bedingung .L3: # Label zu Schleifen-Körper movl -4(%rbp), %eax # Kopiere long Variable i in Register eax cltq # Konvertiere long in Register eax zu quad-word in Register rax movzbl s(%rax), %eax # Kopiere Byte bei Adresse in Variable s mit Offset in Register rax, konvertiere in mit 0 erweitertes long und speichere in Register eax movsbl %al, %eax # Kopiere vorzeichenbehaftetes Byte in Register al, konvertiere in long und speichere in Register eax movl %eax, %esi # Kopiere den long Wert in Register eax in Register esi movl $.LC1, %edi # Kopiere den Pointer zum konstanten String "%d\n" in Register edi movl $0, %eax # printf soll nicht auf SSE Register zugreifen call printf # Sende String in Register edi mit Zusatzparameter in Register esi zum Ausgabe-Stream addl $1, -4(%rbp) # Inkrementiere long Variable i .L2: # Label zu Schleifen-Kopf movl -4(%rbp), %eax # Kopiere long Variable i in Register eax cltq # Konvertiere long in Register eax zu quad-word in Register rax movzbl s(%rax), %eax # Kopiere Byte bei Adresse in Variable s mit Offset in Register rax, konvertiere in mit 0 erweitertes long und speichere in Register eax testb %al, %al # Logische UND-Verknüpfung. Nur zum Setzen der Status Flag, abhängig vom Wert in Register al jne .L3 # Letztere Bedingung ergab nicht 0? Wenn ja, springe zu Schleifen-Körper movl $s, %esi # Kopiere die long Variable s in Register esi movl $.LC2, %edi # Kopiere den Pointer zum konstanten String "%s\n" in Register edi movl $0, %eax # printf soll nicht auf SSE Register zugreifen call printf # Sende String in Register edi mit Zusatzparameter in Register esi zum Ausgabe-Stream movl $0, %eax # Speichere Rückgabeparameter der Funktion in Register eax leave # Setze rsp auf rbp und stelle rbp vom Stack wieder her ret # Funktion verlassen / zur Adresse in rsp springen
Sieh dir genau Zeile 21-25 an. Dort wird i zum Laden des Array Elements genutzt, dieses auf den Wert 0 überprüft und abhängig davon der Sprung vollzogen.
-
Youka schrieb:
# Reserviere Platz auf dem Stack in der Größe von 16 Quad-Words (=16*8 Byte)
es sind 16 bytes.
Youka schrieb:
# Funktion verlassen / zur Adresse in rbp springen
die Rücksprungadresse liegt auf dem Stack ([rsp]).
-
testb %al, %al % setzen von zf, wenn al = 0 => zf = 1 jne .L3 % jump wenn zf = 0, d.h. springe wenn al != 0, setze schleife fort, wenn s[i] != 0, ...
-
Youka schrieb:
main: # Setze Funktions-Label .LFB0: # Setze Label zu Funktionsbeginn pushq %rbp # Sichere den Frame Pointer auf dem Stack movq %rsp, %rbp # Kopiere den Stack Pointer zum Frame Pointer subq $16, %rsp # Reserviere Platz auf dem Stack in der Größe von 16 Quad-Words (=16*8 Byte) movl $.LC0, %edi # Kopiere den Pointer zum konstanten String "Hallo" in Register edi call puts # Sende den String Pointer in edi zum Ausgabe-Stream movl $0, -4(%rbp) # Nutze den reservierten Stack Raum für long Variable i und setze ihren Wert auf 0 jmp .L2 # Springe zu Schleifen-Kopf / prüfe Schleifen-Bedingung .L3: # Label zu Schleifen-Körper movl -4(%rbp), %eax # Kopiere long Variable i in Register eax cltq # Konvertiere long in Register eax zu quad-word in Register rax movzbl s(%rax), %eax # Kopiere Byte bei Adresse in Variable s mit Offset in Register rax, konvertiere in mit 0 erweitertes long und speichere in Register eax movsbl %al, %eax # Kopiere vorzeichenbehaftetes Byte in Register al, konvertiere in long und speichere in Register eax movl %eax, %esi # Kopiere den long Wert in Register eax in Register esi movl $.LC1, %edi # Kopiere den Pointer zum konstanten String "%d\n" in Register edi movl $0, %eax # printf soll nicht auf SSE Register zugreifen call printf # Sende String in Register edi mit Zusatzparameter in Register esi zum Ausgabe-Stream addl $1, -4(%rbp) # Inkrementiere long Variable i .L2: # Label zu Schleifen-Kopf movl -4(%rbp), %eax # Kopiere long Variable i in Register eax cltq # Konvertiere long in Register eax zu quad-word in Register rax movzbl s(%rax), %eax # Kopiere Byte bei Adresse in Variable s mit Offset in Register rax, konvertiere in mit 0 erweitertes long und speichere in Register eax testb %al, %al # Logische UND-Verknüpfung. Nur zum Setzen der Status Flag, abhängig vom Wert in Register al jne .L3 # Letztere Bedingung ergab nicht 0? Wenn ja, springe zu Schleifen-Körper movl $s, %esi # Kopiere die long Variable s in Register esi movl $.LC2, %edi # Kopiere den Pointer zum konstanten String "%s\n" in Register edi movl $0, %eax # printf soll nicht auf SSE Register zugreifen call printf # Sende String in Register edi mit Zusatzparameter in Register esi zum Ausgabe-Stream movl $0, %eax # Speichere Rückgabeparameter der Funktion in Register eax leave # Setze rsp auf rbp und stelle rbp vom Stack wieder her ret # Funktion verlassen / zur Adresse in rbp springen
Sieh dir genau Zeile 21-25 an. Dort wird i zum Laden des Array Elements genutzt, dieses auf den Wert 0 überprüft und abhängig davon der Sprung vollzogen.
Danke für die ausführliche Antwort. Um das nochmal genau nachzuvollziehen:
.L2: movl -4(%rbp), %eax # Lade i nach %eax cltq # Erweitere %eax zu %rax movzbl s(%rax), %eax # Lade Byte (s - %rax) nach %eax testb %al, %al # Teste ob %al != 0 jne .L3 # Wenn %al != 0, dann springe zu .L3
Also wird die Schleife abgebrochen, wenn in %al 0 steht. Nun schaut mein Byte-Array aber so aus:
s: .byte 9 .byte 46 .byte 115 … .byte 10 .section .rodata
Ich weiß jetzt leider immer noch nicht so wirklich, wo die Null herkommt, damit die Schleife abbricht.
-
Nun schaut mein Byte-Array aber so aus:
Dein C-Programm ist fehlerhaft.
-
Michamab schrieb:
Also wird die Schleife abgebrochen, wenn in %al 0 steht. Nun schaut mein Byte-Array aber so aus:
s: .byte 9 .byte 46 .byte 115 … .byte 10 .section .rodata
Ich weiß jetzt leider immer noch nicht so wirklich, wo die Null herkommt, damit die Schleife abbricht.
Das frage ich mich nun aber auch, da ich die ... für einen Filler hielt und annahm, s wäre ein mit '\0' terminierender String, wie er auch in Zeile 8 deines C Codes verwendet wird. Andernfalls würde es spätestens in deiner Schleife nach dem letzten Array-Element zu einer Zugriffsverletzung kommen.
-
Michamab schrieb:
Ich weiß jetzt leider immer noch nicht so wirklich, wo die Null herkommt, damit die Schleife abbricht.
Die Null musst du selbst zum Array hinzufügen.
char s[] = {'A','B','C', '\0'}; // ^ hier // rest ist ok...
-
Danke für die Anmerkungen. Das ist natürlich ziemlich logisch, komisch das es trotzdem funktioniert hat.