Spiega in poche parole il concetto di uno stack frame


200

Mi sembra di avere l'idea di stack di chiamate nella progettazione del linguaggio di programmazione. Ma non riesco a trovare (probabilmente, non cerco abbastanza bene) nessuna spiegazione decente di cosa sia lo stack frame .

Quindi vorrei chiedere a qualcuno di spiegarmelo in poche parole.

Risposte:


195

Un frame dello stack è un frame di dati che viene inserito nello stack. Nel caso di uno stack di chiamate, un frame dello stack rappresenterebbe una chiamata di funzione e i relativi dati dell'argomento.

Se ricordo bene, l'indirizzo di ritorno della funzione viene prima inserito nello stack, quindi gli argomenti e lo spazio per le variabili locali. Insieme, creano il "frame", sebbene questo sia probabilmente dipendente dall'architettura. Il processore sa quanti byte ci sono in ogni frame e sposta il puntatore dello stack di conseguenza quando i frame vengono spinti e saltati fuori dallo stack.

MODIFICARE:

Esiste una grande differenza tra stack di chiamate di livello superiore e stack di chiamate del processore.

Quando parliamo di stack di chiamate di un processore, stiamo parlando di lavorare con indirizzi e valori a livello di byte / parola nell'assembly o nel codice macchina. Esistono "stack di chiamate" quando si parla di linguaggi di livello superiore, ma sono uno strumento di debug / runtime gestito dall'ambiente di runtime in modo da poter registrare ciò che è andato storto con il proprio programma (ad alto livello). A questo livello, sono spesso noti elementi come numeri di riga, nomi di metodi e classi. Quando il processore ottiene il codice, non ha assolutamente idea di queste cose.


6
"Il processore sa quanti byte ci sono in ogni frame e sposta il puntatore dello stack di conseguenza quando i frame vengono spinti e saltati fuori dallo stack." - Dubito che il processore sappia qualcosa dello stack, perché lo manipoliamo tramite subbing (allocazione), push e popping. E così ecco le convenzioni di chiamata che spiegano come dovremmo usare lo stack.
Victor Polevoy,

78

Se capisci molto bene lo stack, allora capirai come funziona la memoria nel programma e se capisci come funziona la memoria nel programma, capirai come l'archivio funzioni nel programma e se capisci come l'archivio funzioni nel programma capirai come funziona la funzione ricorsiva 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

Lasciami spiegare come funziona lo stack:

Per prima cosa devi sapere come sono rappresentate le funzioni nello stack:

L'heap memorizza i valori allocati dinamicamente.
Lo stack memorizza i valori di allocazione e cancellazione automatici.

inserisci qui la descrizione dell'immagine

Comprendiamo con esempio:

def hello(x):
    if x==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 capisci parti di questo programma:

inserisci qui la descrizione dell'immagine

Ora vediamo cos'è lo stack e quali sono le parti dello stack:

inserisci qui la descrizione dell'immagine

Allocazione dello stack:

Ricorda una cosa: se una condizione di ritorno di una funzione viene soddisfatta, indipendentemente dal fatto che abbia caricato o meno le variabili locali, tornerà immediatamente dallo stack con il suo frame di stack. Ciò significa che ogni qualvolta una funzione ricorsiva soddisfa la condizione di base e inseriamo un ritorno dopo la condizione di base, la condizione di base non attende il caricamento di variabili locali che si trovano nella parte "else" del programma. Restituirà immediatamente il frame corrente dallo stack a seguito del quale il frame successivo è ora nel record di attivazione.

Vedi questo in pratica:

inserisci qui la descrizione dell'immagine

Deallocation del blocco:

Quindi ora ogni volta che una funzione incontra un'istruzione return, elimina il frame corrente dallo stack.

Durante la restituzione dallo stack, i valori verranno restituiti al contrario dell'ordine originale in cui sono stati allocati nello stack.

inserisci qui la descrizione dell'immagine


3
la pila cresce verso il basso e la pila cresce verso l'alto, li hai invertiti nel diagramma. DIAGRAMMA CORRETTO QUI
Rafael,

@Rafael scusate la confusione, stavo parlando della direzione della crescita, non stavo parlando della direzione della crescita dello stack. Ci sono differenze tra la direzione della crescita e la direzione della crescita dello stack. Vedi qui stackoverflow.com/questions/1677415/...
Aaditya Ura

2
Rafael ha ragione. Anche la prima foto è sbagliata. Sostituiscilo con qualcos'altro (cerca nelle immagini di Google "heap stack").
Nikos,

Quindi, se ho capito bene, nel tuo terzo diagramma, ci sono 3 frame di stack perché hello()ha chiamato in modo ricorsivo hello()che ha (di nuovo) ricorsivamente chiamato hello()e il frame globale è la funzione originale che ha chiamato il primo hello()?
Andy J,

1
Dove ci portano i collegamenti ?? In quanto seri problemi di sicurezza, questi collegamenti dovrebbero essere rimossi il più presto possibile.
Shivanshu,

45

Un rapido riassunto. Forse qualcuno ha una spiegazione migliore.

Uno stack di chiamate è composto da 1 o più frame di stack diversi. Ogni frame dello stack corrisponde a una chiamata a una funzione o procedura che non è ancora terminata con un ritorno.

Per usare uno stack frame, un thread mantiene due puntatori, uno si chiama Stack Pointer (SP) e l'altro si chiama Frame Pointer (FP). SP punta sempre alla "cima" dello stack e FP punta sempre alla "cima" del frame. Inoltre, il thread mantiene anche un contatore di programmi (PC) che punta all'istruzione successiva da eseguire.

Sono archiviati nello stack: variabili e temporali locali, parametri effettivi dell'istruzione corrente (procedura, funzione, ecc.)

Esistono diverse convenzioni di chiamata relative alla pulizia dello stack.


7
Non dimenticare che l'indirizzo di ritorno della subroutine va nello stack.
Tony R

4
Frame Pointer è anche Base Pointer in termini x86
peterchaula

1
Vorrei sottolineare che un puntatore a frame punta all'inizio del frame dello stack per l'incarnazione della procedura attualmente attiva.
Server Khalilov il

13

"Uno stack di chiamate è composto da frame di stack ..." -  Wikipedia

Un frame dello stack è una cosa che hai messo nello stack. Sono strutture di dati che contengono informazioni sulle subroutine da chiamare.


Spiacente, non ho idea di come mi sia perso questo nel wiki. Grazie. Capisco correttamente che nelle lingue dinamiche la dimensione della cornice non è un valore costante poiché i locali della funzione non sono esattamente noti?
ikostia,

Le dimensioni e la natura di un telaio dipendono fortemente dall'architettura della macchina. In effetti, il paradigma stesso di uno stack di chiamate è specifico dell'architettura. Per quanto ne so, è sempre variabile perché chiamate di funzioni diverse avranno quantità diverse di dati dell'argomento.
Tony R

Si noti che la dimensione del frame dello stack deve essere nota dal processore quando viene manipolata. Quando ciò accade, la dimensione dei dati è già determinata. I linguaggi dinamici vengono compilati per codice macchina proprio come i linguaggi statici, ma spesso vengono eseguiti just-in-time in modo che il compilatore possa mantenere il dinamismo e il processore possa lavorare con frame "noti". Non confondere i linguaggi di livello superiore con il codice / assembly della macchina, che è dove stanno realmente accadendo queste cose.
Tony R

Bene, ma anche i linguaggi dinamici hanno le loro pile di chiamate, vero? Voglio dire, se, diciamo, Python vuole eseguire qualche procedura, i dati su questa procedura sono memorizzati all'interno della struttura di alcuni interpreti Python, ho ragione? Quindi intendo che lo stack di chiamate è presente non solo a un livello basso.
ikostia,

Dopo aver letto un po 'di quell'articolo di Wikipedia, rimango corretto (un po'). Le dimensioni del frame dello stack possono rimanere sconosciute al momento della compilazione . Ma quando il processore funziona con i puntatori stack + frame, deve sapere quali sono le dimensioni. Le dimensioni possono essere variabili ma il processore conosce le dimensioni, è quello che stavo cercando di dire.
Tony R

5

I programmatori possono avere domande sui frame dello stack non in senso lato (che si tratta di un'entità singola nello stack che serve solo una chiamata di funzione e mantiene l'indirizzo di ritorno, argomenti e variabili locali) ma in senso stretto - quando il termine stack framesè menzionato in contesto delle opzioni del compilatore.

Se l'autore della domanda lo abbia o meno significato, ma il concetto di stack frame dall'aspetto delle opzioni del compilatore è un problema molto importante, non trattato dalle altre risposte qui.

Ad esempio, il compilatore C / C ++ di Microsoft Visual Studio 2015 ha la seguente opzione relativa a stack frames:

  • / Oy (Omissione del puntatore al telaio)

GCC ha il seguente:

  • -fomit-frame-pointer (Non tenere il puntatore del frame in un registro per le funzioni che non ne hanno bisogno. Questo evita le istruzioni per salvare, impostare e ripristinare i puntatori del frame; rende anche disponibile un registro extra in molte funzioni )

Il compilatore Intel C ++ ha il seguente:

  • -fomit-frame-pointer (Determina se l'EBP viene utilizzato come registro per scopi generici nelle ottimizzazioni)

che ha il seguente alias:

  • / Oy

Delphi ha la seguente opzione da riga di comando:

  • - $ W + (Genera frame stack)

In questo senso specifico, dal punto di vista del compilatore, un frame dello stack è solo il codice di entrata e uscita per la routine , che spinge un ancoraggio nello stack - che può anche essere usato per il debug e per la gestione delle eccezioni. Gli strumenti di debug possono scansionare i dati dello stack e usare questi ancoraggi per il backtracing, mentre si trovano call sitesnello stack, cioè per visualizzare i nomi delle funzioni nell'ordine in cui sono stati chiamati gerarchicamente. Per l'architettura Intel, è push ebp; mov ebp, espo enterper entrata e / mov esp, ebp; pop ebpo leaveuscita.

Ecco perché è molto importante capire per un programmatore in cosa si trova un frame di stack quando si tratta di opzioni del compilatore, perché il compilatore può controllare se generare questo codice o meno.

In alcuni casi, il compilatore può omettere il frame dello stack (codice di entrata e uscita per la routine) e sarà possibile accedere direttamente alle variabili tramite il puntatore dello stack (SP / ESP / RSP) anziché il comodo puntatore di base (BP / ESP / RSP). Condizioni per l'omissione del frame dello stack, ad esempio:

  • la funzione è una funzione foglia (cioè un'entità finale che non chiama altre funzioni);
  • non ci sono costrutti try / finally o try / tranne o simili, ovvero non vengono utilizzate eccezioni;
  • non vengono chiamate routine con parametri in uscita nello stack;
  • la funzione non ha parametri;
  • la funzione non ha un codice assembly incorporato;
  • eccetera...

L'omissione di frame di stack (codice di entrata e di uscita per la routine) può rendere il codice più piccolo e più veloce, ma può anche influire negativamente sulla capacità dei debugger di risalire ai dati nello stack e mostrarli al programmatore. Queste sono le opzioni del compilatore che determinano a quali condizioni una funzione dovrebbe avere il codice di entrata e uscita, ad esempio: (a) sempre, (b) mai, (c) quando necessario (specificando le condizioni).


-1

Stack frame sono le informazioni impacchettate relative a una chiamata di funzione. Queste informazioni generalmente includono argomenti passati alla funzione, variabili locali e dove tornare al termine. Il record di attivazione è un altro nome per un frame dello stack. Il layout del frame dello stack è determinato dall'ABI dal produttore e ogni compilatore che supporta l'ISA deve essere conforme a questo standard, tuttavia lo schema di layout può dipendere dal compilatore. Generalmente le dimensioni del frame dello stack non sono limitate, ma esiste un concetto chiamato "zona rossa / protetta" per consentire l'esecuzione delle chiamate di sistema ... ecc. Senza interferire con un frame dello stack.

C'è sempre un SP ma su alcuni ABI (ad esempio ARM e PowerPC) FP è opzionale. Gli argomenti che dovevano essere posizionati nello stack possono essere compensati usando solo SP. La generazione o meno di un frame dello stack per una chiamata di funzione dipende dal tipo e dal numero di argomenti, dalle variabili locali e dal modo in cui le variabili locali sono accessibili in generale. Sulla maggior parte degli ISA, in primo luogo, vengono utilizzati i registri e se sono presenti più argomenti rispetto ai registri dedicati al passaggio degli argomenti, questi vengono inseriti nello stack (ad esempio, l'ABI x86 ha 6 registri per passare argomenti interi). Quindi, a volte, alcune funzioni non hanno bisogno di un frame dello stack da posizionare nello stack, ma l'indirizzo di ritorno viene inserito nello stack.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.