Arbeitsweise eines Stacks



  • Hallo Zusammen!
    Ich habe die Arbeitsweise des Stacks leider nicht verstehen können,
    wirds jedenfalls in einem Buch beschrieben. Für meinen Geschmack etwas unverständlich. kann mir bitte jemand ganz vereinfacht die Arbeitsweise des Stacks mit eigenen Worten erklären?

    Zitat:
    laut Buch
    Die Analogie zum Geschirrstapel eignet sich zwar zur anschaulichen Darstellung, versagt aber bei der grundlegenden Arbeitsweise des Stacks. Eine genauere Vorstellung liefert eine Folge von Fächern, die von oben nach unten angeordnet sind. Auf der Spitze des Stacks befindet sich das Fach, auf das der Stack-Pointer zeigt. Alle Fächer haben eine forlaufende Adresse und eine dieser Adressen wird im Stack-Register abgelegt. Alles unterhalb dieser magischen Adresse, die man als Spitze des Stacks bezeichnet, wird als auf dem Stack befindlich betrachtet.
    Alles oberhalb des Stack-Pointers liegt außerhalb des Stacks und ist ungültig.



  • Moin, Moin...

    Der Stack ist erstmal ein bestimmter Bereich im Speicher. Dieser Speicherbereich wird vom Prozessor verwendet, um z.B. Rücksprungadressen aus Funktionen abzulegen. Dazu gibt es einen Zeiger, eine Adresse, der auf die Spitze des Stacks zeigt. Der Stack wächst von oben(hohe Adressen) nach unten(niedrige Adressen).

    Beispiel (16 Bit):

    Adresse    Inhalt       Stackpointer(sp)    
    01004h     00001h
    01002h     08976h
    01000h     0abcdh       <== 01000h      
    00ffeh     undefiniert         
    00ffch     undefiniert        
    00ffah     undefiniert         
    
    Befehl     push 01234h
    
               Der Wert 01234h wird auf den Stack gelegt. Dazu wird der  
               Stackpointer um 2 verringert und der Wert in den Speicher
               Geschrieben, ab der Adresse auf die sp jetzt zeigt. Alle 
               Werte, die unterhalb des sp stehen, gelten als undefiniert.
    
    Adresse    Inhalt       Stackpointer(sp)    
    01004h     00001h
    01002h     08976h
    01000h     0abcdh             
    00ffeh     01234h       <== 00ffeh       
    00ffch     undefiniert        
    00ffah     undefiniert         
    
    Befehl     push 08989h
    
    Adresse    Inhalt       Stackpointer(sp)    
    01004h     00001h
    01002h     08976h
    01000h     0abcdh             
    00ffeh     01234h              
    00ffch     08989h       <== 00ffch      
    00ffah     undefiniert         
    
    Befehl     pop ax
    
               Der Wert auf den der Stackpointer zeigt, wird in das  
               Register ax kopiert und dann sp um 2 erhöht. Alle 
               Werte, die unterhalb des neuen sp stehen, gelten als 
               undefiniert.
    
    Adresse    Inhalt       Stackpointer(sp)    
    01004h     00001h
    01002h     08976h
    01000h     0abcdh             
    00ffeh     01234h       <== 00ffeh       
    00ffch     undefiniert             
    00ffah     undefiniert
    

    Ciao...



  • Das mit dem Geschirrstapel passt schon, nur dass sich das Geschirr normalerweise von unten nach oben stapelt, der Stack (auf x86 und den meisten CPUs zumindest) aber von oben nach unten.

    Du machst:

    push 1
    push 2
    push 3

    Du bekommst:

    pop -> 3
    pop -> 2
    pop -> 1

    Oder

    push 1
    push 2
    pop -> 2
    push 3
    push 4
    pop -> 4
    pop -> 3
    pop -> 1



  • auf IA32 arbeitet der Stack (Stackpointer %esp) wie bereits gesagt von oben nach unten, wenn du nun Speicher auf dem Stack reservieren willst, ziehst du einfach die Anzahl der bytes vom Stackpointer ab

    int main() {
      int a;
    }
    

    sehe dann so aus (bei 4byte pro int)

    main:
    #Funktions Initialisierung erklär ich gleich
            subl    $4, %esp
    #Funktions Deinitialisierung erklär ich auch gleich
    

    in C und C++ räumt man nach dem schließen eines Scopes ( { } ) den Stack wieder auf, also man wirft alles runter was darauf liegt, dass sieht dann beim GCC so aus

    main:
      pushl %ebp
      movl %esp, %ebp #stack pointer wird in %ebp gespeichert
    
      subl $4, %esp #speicher für int a reservieren
    
      #... hier sind noch uninteressante Sachen, wie Rückgabewert etc.
    
      movl %ebp, %esp #so der stack zeigt auf das gleiche wie am Anfang
      popl %ebp
      ret
    


  • int main() {
      int a;
      a=0xBABE;
    }
    

    also der Zugriff auf den reservierten Stack Speicher sieht dann so aus

    main:
      pushl %ebp
      movl %esp, %ebp #stack pointer wird in %ebp gespeichert
    
      subl $4, %esp #speicher für int a reservieren
      movl $0xBABE, -4(%ebp) #4 byte abziehen vom Anfang des Stacks
    
      #... hier sind noch uninteressante Sachen, wie Rückgabewert etc.
    
      movl %ebp, %esp #so der stack zeigt auf das gleiche wie am Anfang
      popl %ebp
      ret
    


  • Gehen wir mal davon aus das der Stack 100 dec Byte groß ist.

    Speicheradresse  AsBefehl     Stackpointer Stackinhalt
    
           10        call func          99       Adresse nach dem Call (13) auf 
                                                 den Stack
                     Rückkehr aus func 100       Nichts
           13        push al            99       Inhalt von AL auf den Stack
           14        pop  al           100       Al vom Stack         
    ;///
    func:                               99       13(Adresse nach dem Call)
           40        Assemblercode
                     ....
                     ret               100       Der Wert 13(Rücksprung Adresse)
                                                 wird durch ret vom Stack geholt 
                                                 um zur Adresse nach dem Aufruf 
                                                 der Function zurückzukehren. 
    
    Es ist Fatal, wenn man z.B in einer aufgerufen Function Register auf den
    Stack packt und dann vergisst sie wieder vom Stack zu holen.
    
    func:                               99       13(Adresse nach dem Call)
           40        Assemblercode
                     ....
                     push al            98       Ihalt von AL auf den Stack 
                     ret                99       Hier wird nun nicht die 
                                                 eigentliche Rücksprungadresse
                                                 vom Stack geholt sonder der
                                                 vorher auf den Stack gepackte
                                                 Wert von al, was zu einem        
                                                 Absturz führt.
    


  • Podschun wählt im Assembler-Buch den "anschaulichen" Vergleich mit einer Stalagtite!



  • hi,
    stacks sind eine häufig eingesetzte datenstruktur z.b. in parsern o.ä.
    man solltet stacks nicht immer nur auf assembler-level betrachten
    guckt ihr hier: http://www.cs.bu.edu/teaching/c/stack/


Anmelden zum Antworten