Comprensione dello stack frame della funzione call in C / C ++?

15

Sto cercando di capire come vengono costruiti i frame di stack e quali variabili (params) vengono spinte per impilare in quale ordine? Alcuni risultati di ricerca hanno mostrato che il compilatore C / C ++ decide in base alle operazioni eseguite all'interno di una funzione. Ad esempio, se si supponeva che la funzione aumentasse di un valore int inoltrato di 1 (simile all'operatore ++) e la restituisse, inserirà tutti i parametri della funzione e le variabili locali nei registri.

Mi chiedo quali registri vengano usati per i parametri restituiti o passati per valore. Come vengono restituiti i riferimenti? Come fa il compilatore a scegliere tra eax, ebx, ecx ed edx?

Che cosa devo sapere per capire come i registri, i riferimenti stack e heap sono usati, costruiti e distrutti durante le chiamate di funzione?

    
posta Gana 18.04.2013 - 18:58
fonte

4 risposte

8

Oltre a ciò che Dirk ha detto, un uso importante dei frame dello stack è quello di salvare i valori precedenti dei registri in modo che possano essere ripristinati dopo una chiamata di funzione. Pertanto, anche nei processori in cui i registri vengono utilizzati per il passaggio di parametri, la restituzione di un valore e il salvataggio dell'indirizzo di ritorno, i valori di tali registri vengono salvati nello stack prima di una chiamata di funzione in modo che possano essere ripristinati dopo la chiamata. Ciò consente a una funzione di chiamarne un'altra senza sovrascrivere i propri parametri o dimenticare il proprio indirizzo di ritorno.

Quindi, chiamare una funzione B dalla funzione A su un tipico sistema "generico" potrebbe comportare i seguenti passaggi:

  • funzione A:
    • spazia spazio per il valore di ritorno
    • imposta i parametri
    • inserisci l'indirizzo di ritorno
  • passa alla funzione B
  • funzione B:
    • inserisci l'indirizzo del frame dello stack precedente
    • premi i valori dei registri utilizzati da questa funzione (in modo che possano essere ripristinati)
    • spinga lo spazio per le variabili locali
    • esegui il calcolo necessario
    • ripristina i registri
    • ripristina il frame dello stack precedente
    • memorizza il risultato della funzione
    • passa all'indirizzo di ritorno
  • funzione A:
    • inserisci i parametri
    • inserisci il valore restituito

Questo non è affatto l'unico modo in cui le chiamate di funzione possono funzionare (e posso avere un passo o due fuori uso), ma dovrebbe darti un'idea di come lo stack viene utilizzato per consentire al processore di gestire le chiamate di funzioni annidate .

    
risposta data 18.04.2013 - 23:50
fonte
9

Dipende dalla convenzione di chiamata utilizzata. Chiunque definisca la convenzione di chiamata può prendere questa decisione come preferisce.

Nella convenzione di chiamata più comune su x86, i registri non vengono utilizzati per il passaggio dei parametri; i parametri vengono inseriti nello stack iniziando dal parametro più a destra. Il valore di ritorno è posto in eax e può usare edx se ha bisogno di spazio aggiuntivo. I riferimenti e i puntatori vengono entrambi restituiti sotto forma di indirizzo in eax.

    
risposta data 18.04.2013 - 19:25
fonte
4

Se capisci molto bene lo stack, capirai come funziona la memoria in programma e se capisci come funziona la memoria in programma capirai come la funzione memorizza in programma e se capisci come la funzione memorizza in programma capirai come funzione ricorsiva funziona e se capisci come funziona la funzione ricorsiva capirai come funziona il compilatore e se capisci come funziona il compilatore la tua mente funzionerà come compilatore e eseguirai il debug di qualsiasi programma molto facilmente

Lascia che ti spieghi come funziona lo stack:

Per prima cosa devi sapere come memorizzare le funzioni nello stack:

Valori di allocazione della memoria dinamica del deposito heap. Assegnazione automatica degli stack e valori di cancellazione.

Comprendiamoconl'esempio:

defhello(x):ifx==1:return"op"
    else:
        u=1
        e=12
        s=hello(x-1)
        e+=1
        print(s)
        print(x)
        u+=1
    return e

hello(4)

Ora comprendi parti di questo programma:

Oravediamocosaèlostackequalisonolepartidellostack:

Allocationofthestack:

Ricordaunacosa,seunaqualsiasifunzioneottiene"return", indipendentemente dal fatto che abbia caricato tutte le sue varibles locali o qualsiasi cosa restituisca immediatamente dallo stack il suo stack frame. Significa che quando una funzione ricorsiva ottiene la condizione di base e noi restituiamo il ritorno dopo la condizione di base, quindi la condizione di base non aspetterà di caricare le variabili locali che si trovano nella parte "else" del programma restituirà immediatamente il frame corrente dallo stack e ora se un frame return next frame è nel record di attivazione. Vedi questo in pratica:

Deallocationoftheblock:

Quindioraognivoltacheunafunzionetrovaun'istruzionereturn,cancellailframecorrentedallostack.

mentreilritornodalvaloredellostackritorneràinordineinversodiordineincuisonostatiallocatinellostack.

Questesonodescrizionimoltobreviesevuoisapernedipiùapprofonditamentesustackedoppiaricorsioneleggiduepostdiquestoblog:

Ulteriori informazioni sullo stack passo dopo passo

Ulteriori informazioni sulla doppia ricorsione passo per passo con lo stack

    
risposta data 18.10.2016 - 13:05
fonte
3

Quello che stai cercando si chiama Application Binary Interface - ABI.

C'è una specifica per ogni compilatore che spiega l'ABI.

Ogni piattaforma di solito specifica e ABI al fine di supportare l'interoperabilità tra i compilatori. Ad esempio, le x86 Calling Conventions spiegano le tipiche convenzioni di chiamata per x86 e x86-64. Mi aspetterei comunque un documento più ufficiale di wikipedia.

    
risposta data 19.04.2013 - 03:56
fonte

Leggi altre domande sui tag