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.


Anmelden zum Antworten