Come vengono archiviate e recuperate le variabili dallo stack del programma?


47

Ci scusiamo in anticipo per l'ingenuità di questa domanda. Sono un artista di 50 anni che cerca di capire correttamente i computer per la prima volta. Quindi ecco qui.

Ho cercato di capire come i tipi di dati e le variabili sono gestiti da un compilatore (in senso molto generale, so che c'è molto da fare). Mi manca qualcosa nella mia comprensione della relazione tra memoria nello "stack" e tipi di valore e memoria nello "heap" e tipi di riferimento (le virgolette intendono significare che capisco che questi termini sono astrazioni e non essere preso troppo alla lettera in un contesto così semplificato come il modo in cui sto formulando questa domanda). Comunque, la mia idea semplicistica è che tipi come i booleani e gli interi vanno "in pila" perché possono, perché sono entità conosciute in termini di spazio di archiviazione e il loro ambito è facilmente controllato di conseguenza.

Ma quello che non capisco è come le variabili nello stack vengono quindi lette da un'applicazione - se dichiaro e lo assegno xcome numero intero, diciamo x = 3, e lo spazio di archiviazione è riservato nello stack e quindi il suo valore 3è memorizzato lì, e quindi in la stessa funzione che dichiaro e assegno ycome, diciamo 4, e poi che uso xin un'altra espressione, (diciamo z = 5 + x) come può leggere il programma xper valutare zquando è sottoyin pila? Mi manca chiaramente qualcosa. La posizione nello stack riguarda solo la durata / ambito della variabile e l'intero stack è effettivamente accessibile al programma in ogni momento? In tal caso, ciò implica che esiste un altro indice che contiene solo gli indirizzi delle variabili nello stack per consentire il recupero dei valori? Ma poi ho pensato che l'intero punto dello stack fosse che i valori erano memorizzati nello stesso posto dell'indirizzo variabile? Nella mia mente scadente sembra che se c'è questo altro indice, allora stiamo parlando di qualcosa di più simile a un mucchio? Sono chiaramente molto confuso e spero solo che ci sia una risposta semplice alla mia domanda semplicistica.

Grazie per aver letto fin qui.


7
@ fade2black Non sono d'accordo - dovrebbe essere possibile dare una risposta di ragionevole lunghezza che riassuma i punti importanti.
David Richerby,

9
Stai commettendo l'errore estremamente comune di confondere il tipo di valore con il punto in cui è memorizzato . È semplicemente falso dire che i bool vanno in pila. I bool vanno in variabili e le variabili vanno in pila se si sa che le loro vite sono brevi e sull'heap se non si sa che le loro vite sono brevi. Per alcune riflessioni su come ciò si riferisce a C #, vedi blogs.msdn.microsoft.com/ericlippert/2010/09/30/…
Eric Lippert,

7
Inoltre, non pensare allo stack come a uno stack di valori nelle variabili . Pensalo come una pila di frame di attivazione per i metodi . All'interno di un metodo è possibile accedere a qualsiasi variabile dell'attivazione di quel metodo, ma non è possibile accedere alle variabili del chiamante, poiché non si trovano nel frame in cima allo stack .
Eric Lippert,

5
Inoltre: ti applaudo per aver preso l'iniziativa di imparare qualcosa di nuovo e di approfondire i dettagli di implementazione di una lingua. Ti stai imbattendo in un interessante ostacolo qui: capisci cos'è uno stack come un tipo di dati astratto , ma non come un dettaglio di implementazione per reificare l'attivazione e la continuazione . Quest'ultimo non segue le regole del tipo di dati astratto dello stack; li tratta più come linee guida che come regole. Il punto centrale dei linguaggi di programmazione è assicurarsi di non dover comprendere questi dettagli astratti per risolvere i problemi di programmazione.
Eric Lippert,

4
Grazie Eric, Sava, Miniatura, quei commenti e riferimenti sono tutti estremamente utili. Ho sempre la sensazione che le persone come te debbano gemere interiormente quando vedono una domanda come la mia, ma per favore conosci l'enorme eccitazione e soddisfazione nel ricevere risposte!
Celine Atwood,

Risposte:


24

La memorizzazione di variabili locali su uno stack è un dettaglio di implementazione, sostanzialmente un'ottimizzazione. Puoi pensarlo in questo modo. Quando si inserisce una funzione, lo spazio per tutte le variabili locali viene allocato da qualche parte. È quindi possibile accedere a tutte le variabili, poiché si conosce in qualche modo la loro posizione (questo fa parte del processo di allocazione). Quando si lascia una funzione, lo spazio viene deallocato (liberato).

Lo stack è un modo per implementare questo processo: puoi pensarlo come una sorta di "heap veloce" che ha dimensioni limitate e quindi è appropriato solo per piccole variabili. Come ulteriore ottimizzazione, tutte le variabili locali sono memorizzate in un blocco. Poiché ogni variabile locale ha dimensioni note, si conosce l'offset di ciascuna variabile nel blocco ed è così che si accede ad essa. Ciò è in contrasto con le variabili allocate sull'heap, i cui indirizzi sono essi stessi memorizzati in altre variabili.

Puoi pensare allo stack come molto simile alla classica struttura di dati dello stack, con una differenza cruciale: ti è permesso accedere agli oggetti sotto lo stack. In effetti, puoi accedere al ° elemento dall'alto. Ecco come è possibile accedere a tutte le variabili locali con push e popping. L'unica spinta che si sta facendo è quando si accede alla funzione, e l'unico scoppio quando si esce dalla funzione.k

Infine, vorrei ricordare che in pratica alcune delle variabili locali sono memorizzate nei registri. Questo perché l'accesso ai registri è più veloce dell'accesso allo stack. Questo è un altro modo di implementare uno spazio per le variabili locali. Ancora una volta, sappiamo esattamente dove è memorizzata una variabile (questa volta non tramite offset, ma tramite il nome di un registro) e questo tipo di archiviazione è appropriato solo per piccoli dati.


1
"Allocato in un blocco" è un altro dettaglio di implementazione. Non importa, però. Il compilatore sa come è necessaria la memoria per le variabili locali, alloca quella memoria a uno o più blocchi e quindi crea le variabili locali in quella memoria.
Salmi

Grazie, corretto. In effetti, alcuni di questi "blocchi" sono solo dei registri.
Yuval Filmus,

1
Hai solo bisogno dello stack per memorizzare gli indirizzi di ritorno, se quello. È possibile implementare la ricorsione senza uno stack abbastanza facilmente, passando un puntatore all'indirizzo di ritorno sull'heap.
Yuval Filmus,

1
Le pile @MikeCaron non hanno quasi nulla a che fare con la ricorsione. Perché dovresti "spazzare via le variabili" in altre strategie di implementazione?
gardenhead,

1
@gardenhead l'alternativa più ovvia (e quella effettivamente utilizzata / è stata utilizzata) è quella di allocare staticamente le variabili di ciascuna procedura. Veloce, semplice, prevedibile ... ma non è consentita la ricorsione o il rientro. Questo e lo stack convenzionale non sono le uniche alternative ovviamente (allocare dinamicamente tutto è un altro), ma di solito sono quelli da discutere quando si giustificano gli stack :)
hobbs

23

Avere yin pila non impedisce fisicamente xdi accedere, il che, come hai sottolineato, rende le pile di computer diverse dalle altre pile.

Quando viene compilato un programma, anche le posizioni delle variabili nello stack sono predeterminate (nel contesto di una funzione). Nel tuo esempio, se lo stack contiene un xcon un y"sopra", allora il programma sa in anticipo che xsarà 1 articolo sotto la parte superiore dello stack, mentre all'interno della funzione. Poiché l'hardware del computer può richiedere esplicitamente 1 elemento sotto la parte superiore dello stack, il computer può ottenere xanche se yesiste anche.

La posizione nello stack riguarda solo la durata / ambito della variabile e l'intero stack è effettivamente accessibile al programma in ogni momento?

Sì. Quando si esce da una funzione, il puntatore dello stack torna alla posizione precedente, cancellando efficacemente xe y, tecnicamente, rimarranno lì fino a quando la memoria non verrà utilizzata per qualcos'altro. Inoltre, se la tua funzione chiama un'altra funzione, xe ysarà ancora lì e sarà possibile accedervi intenzionalmente andando troppo in basso nello stack.


1
Questa sembra la risposta più pulita finora in termini di non parlare oltre la conoscenza di fondo che OP porta sul tavolo. +1 per il targeting vero e proprio OP!
Ben I.

1
Sono anche d'accordo! Anche se tutte le risposte sono estremamente utili e sono molto grato, il mio post originale era motivato perché ho la sensazione (d) che l'intera cosa pila / heap sia assolutamente fondamentale per capire come si pone la distinzione valore / tipo di riferimento, ma non ho potuto ' t vedi come se tu potessi vedere solo la parte superiore di "la pila". Quindi la tua risposta mi libera da questo. (Ho la stessa sensazione che ho avuto quando ho realizzato per la prima volta tutte le varie leggi del quadrato inverso in fisica semplicemente cadono dalla geometria delle radiazioni che escono da una sfera, e puoi disegnare un semplice diagramma per vederlo.)
Celine Atwood

Lo adoro perché è sempre estremamente utile quando puoi vedere come e perché un fenomeno di livello superiore (ad es. Nella lingua) è in realtà dovuto a un fenomeno più elementare un po 'più in basso nell'albero dell'astrazione. Anche se è abbastanza semplice.
Celine Atwood,

1
@CelineAtwood Ti preghiamo di notare che il tentativo di accedere alle variabili "con la forza" dopo che sono state rimosse dallo stack ti darà un comportamento imprevedibile / indefinito e non dovrebbe essere fatto. Nota che non ho detto "impossibile" in alcune lingue ti permetterà di provarlo. Tuttavia, sarebbe un errore di programmazione e dovrebbe essere evitato.
code_dredd,

12

Per fornire un esempio concreto di come un compilatore gestisce lo stack e come si accede ai valori nello stack, possiamo guardare le rappresentazioni visive, oltre al codice generato da GCCin un ambiente Linux con i386 come architettura di destinazione.

1. Stack cornici

Come sapete, lo stack è una posizione nello spazio degli indirizzi di un processo in esecuzione che viene utilizzato da funzioni o procedure , nel senso che lo spazio viene allocato nello stack per le variabili dichiarate localmente, nonché gli argomenti passati alla funzione ( lo spazio per le variabili dichiarate al di fuori di qualsiasi funzione (ovvero variabili globali) è allocato in una regione diversa nella memoria virtuale). Lo spazio allocato per tutti i dati di una funzione è riferito a uno stack frame . Ecco una rappresentazione visiva di più frame di stack (da Computer Systems: A Programmer's Perspective ):

Frame dello stack CSAPP

2. Gestione dello stack frame e posizione variabile

Affinché i valori scritti nello stack all'interno di un particolare frame dello stack siano gestiti dal compilatore e letti dal programma, è necessario un metodo per calcolare le posizioni di questi valori e recuperare il loro indirizzo di memoria. I registri nella CPU indicati come puntatore di stack e il puntatore di base aiutano in questo.

Il puntatore di base, ebpper convenzione, contiene l'indirizzo di memoria del fondo, o base, dello stack. Le posizioni di tutti i valori all'interno del frame dello stack possono essere calcolate utilizzando l'indirizzo nel puntatore di base come riferimento. Questo è rappresentato nella figura sopra: %ebp + 4è l'indirizzo di memoria memorizzato nel puntatore di base più 4, per esempio.

3. Codice generato dal compilatore

Ma quello che non capisco è come le variabili nello stack vengono quindi lette da un'applicazione: se dichiaro e assegno x come numero intero, dico x = 3, e lo spazio di archiviazione è riservato nello stack e il suo valore di 3 viene archiviato lì, e quindi nella stessa funzione dichiaro e assegno y come, diciamo 4, e quindi dopo che uso quindi x in un'altra espressione, (diciamo z = 5 + x) come può il programma leggere x per valutare z quando è sotto y in pila?

Usiamo un semplice programma di esempio scritto in C per vedere come funziona:

int main(void)
{
        int x = 3;
        int y = 4;
        int z = 5 + x;

        return 0;
}

Esaminiamo il testo dell'assemblaggio prodotto da GCC per questo testo sorgente C (l'ho ripulito un po 'per motivi di chiarezza):

main:
    pushl   %ebp              # save previous frame's base address on stack
    movl    %esp, %ebp        # use current address of stack pointer as new frame base address
    subl    $16, %esp         # allocate 16 bytes of space on stack for function data
    movl    $3, -12(%ebp)     # variable x at address %ebp - 12
    movl    $4, -8(%ebp)      # variable y at address %ebp - 8
    movl    -12(%ebp), %eax   # write x to register %eax
    addl    $5, %eax          # x + 5 = 9
    movl    %eax, -4(%ebp)    # write 9 to address %ebp - 4 - this is z
    movl    $0, %eax
    leave

Rileviamo però che le variabili x, yez sono situati a indirizzi %ebp - 12, %ebp -8e %ebp - 4, rispettivamente. In altre parole, le posizioni delle variabili all'interno del frame dello stack per main()vengono calcolate utilizzando l'indirizzo di memoria salvato nel registro CPU %ebp.

4. I dati in memoria oltre il puntatore dello stack non rientrano nell'ambito

Mi manca chiaramente qualcosa. La posizione nello stack riguarda solo la durata / ambito della variabile e l'intero stack è effettivamente accessibile al programma in ogni momento? In tal caso, ciò implica che esiste un altro indice che contiene solo gli indirizzi delle variabili nello stack per consentire il recupero dei valori? Ma poi ho pensato che l'intero punto dello stack fosse che i valori erano memorizzati nello stesso posto dell'indirizzo variabile?

Lo stack è un'area nella memoria virtuale, il cui uso è gestito dal compilatore. Il compilatore genera codice in modo tale che i valori oltre il puntatore dello stack (valori oltre la parte superiore dello stack) non vengano mai indicati. Quando viene chiamata una funzione, la posizione del puntatore dello stack cambia per creare spazio nello stack ritenuto non "fuori limite", per così dire.

Man mano che le funzioni vengono chiamate e restituite, il puntatore dello stack viene decrementato e incrementato. I dati scritti nello stack non scompaiono dopo che sono fuori dall'ambito, ma il compilatore non genera istruzioni che fanno riferimento a questi dati perché non è possibile per il compilatore calcolare gli indirizzi di questi dati utilizzando %ebpo %esp.

5. Riepilogo

Il codice che può essere eseguito direttamente dalla CPU viene generato dal compilatore. Il compilatore gestisce lo stack, i frame dello stack per le funzioni e i registri della CPU. Una strategia utilizzata da GCC per tenere traccia delle posizioni delle variabili nei frame dello stack nel codice che deve essere eseguito sull'architettura i386 è utilizzare l'indirizzo di memoria nel puntatore della base del frame dello stack %ebp, come riferimento e scrivere i valori delle variabili nelle posizioni nei frame dello stack a offset all'indirizzo in %ebp.


Il mio se chiedessi da dove venisse quell'immagine? Sembra sospettosamente familiare ... :-) Potrebbe essere stato in un libro di testo passato.
The Great Duck,

1
nvmd. Ho appena visto il link. Era quello che pensavo. +1 per la condivisione di quel libro.
The Great Duck,

1
+1 per la demo dell'assembly gcc :)
flow2k

9

Esistono due registri speciali: ESP (puntatore stack) ed EBP (puntatore base). Quando viene invocata una procedura, le prime due operazioni sono in genere

push        ebp  
mov         ebp,esp 

La prima operazione salva il valore dell'EBP nello stack e la seconda operazione carica il valore del puntatore dello stack nel puntatore di base (per accedere alle variabili locali). Pertanto, EBP punta nella stessa posizione di ESP.

L'assemblatore traduce i nomi delle variabili in offset EBP. Ad esempio, se hai due variabili locali x,ye hai qualcosa di simile

  x = 1;
  y = 2;
  return x + y;

allora potrebbe essere tradotto in qualcosa del genere

   push        ebp  
   mov         ebp,esp
   mov  DWORD PTR [ ebp + 6],  1   ;x = 1
   mov  DWORD PTR [ ebp + 14], 2   ;y = 2
   mov  eax, [ ebp + 6 ]
   add  [ ebp + 14 ], eax          ; x + y 
   mov  eax, [ ebp + 14 ] 
   ...  

I valori di offset 6 e 14 vengono calcolati al momento della compilazione.

Questo è approssimativamente come funziona. Fare riferimento a un libro del compilatore per i dettagli.


14
Questo è specifico per Intel x86. Su ARM, viene utilizzato il registro SP (R13) e FP (R11). E su x86, la mancanza di registri significa che i compilatori aggressivi non useranno l'EBP poiché può essere derivato dall'ESP. Questo è chiaro nell'ultimo esempio, in cui tutto l'indirizzamento relativo a EBP può essere tradotto in relativo a ESP, senza che siano necessarie altre modifiche.
MSalters,

Non ti manca un SUB su ESP per fare spazio per x, y in primo luogo?
Hagen von Eitzen,

@HagenvonEitzen, probabilmente. Volevo solo esprimere l'idea di come accedere alle variabili allocate nello stack usando i registri hardware.
fade2black

Downvoter, commenti per favore !!!
fade2black

8

Sei confuso perché le variabili locali memorizzate nello stack non sono accessibili con la regola di accesso dello stack: First In Last Out o semplicemente FILO .

Il fatto è che la regola FILO si applica alle sequenze di chiamate di funzione e agli stack frame , piuttosto che alle variabili locali.

Che cos'è un frame stack?

Quando si inserisce una funzione, viene fornita una certa quantità di memoria nello stack, chiamata frame dello stack. Le variabili locali della funzione sono memorizzate nel frame dello stack. Potete immaginare che le dimensioni del frame dello stack variano da funzione a funzione poiché ogni funzione ha numeri e dimensioni diverse di variabili locali.

Il modo in cui le variabili locali sono archiviate nel frame dello stack non ha nulla a che fare con FILO. (Anche l'ordine di comparsa delle variabili locali nel codice sorgente non garantisce che le variabili locali vengano memorizzate in quell'ordine.) Come hai correttamente dedotto nella tua domanda, "esiste un altro indice che contiene solo gli indirizzi delle variabili nello stack per consentire il recupero dei valori ". Gli indirizzi delle variabili locali sono in genere calcolati con un indirizzo di base , come l'indirizzo al contorno del frame dello stack e valori di offset specifici per ciascuna variabile locale.

Quindi quando appare questo comportamento FILO?

Ora, cosa succede se si chiama un'altra funzione? La funzione di chiamata deve avere un proprio frame dello stack, ed è questo frame dello stack che viene inserito nello stack . Cioè, il frame di stack della funzione di chiamata viene posizionato sopra il frame di stack della funzione di chiamante. E se questa funzione di chiamata chiama un'altra funzione, allora il suo frame dello stack verrà spinto, di nuovo, in cima allo stack.

Cosa succede se viene restituita una funzione? Quando una funzione callee ritorna alla funzione chiamante, stack frame della funzione callee e ' spuntato fuori dalla pila, liberando spazio per un utilizzo futuro.

Quindi dalla tua domanda:

La posizione nello stack riguarda solo la durata / ambito della variabile e l'intero stack è effettivamente accessibile al programma in ogni momento?

sei abbastanza corretto qui perché i valori delle variabili locali sul frame dello stack non vengono realmente cancellati quando la funzione ritorna. Il valore rimane lì, anche se la posizione di memoria in cui è memorizzata non appartiene al frame dello stack di nessuna funzione. Il valore viene cancellato quando un'altra funzione ottiene il suo frame di stack che include la posizione e scrive su un altro valore nella posizione di memoria.

Quindi cosa differenziates stack dall'heap?

Stack e heap sono uguali nel senso che sono entrambi nomi che si riferiscono ad un po 'di spazio in memoria. Poiché possiamo accedere a qualsiasi posizione in memoria con il suo indirizzo, puoi accedere a qualsiasi posizione in pila o heap.

La differenza deriva dalla promessa che il sistema informatico fa su come li userà. Come hai detto, heap è per il tipo di riferimento. Poiché i valori nell'heap non hanno alcuna relazione con uno specifico frame dello stack, l'ambito del valore non è legato a nessuna funzione. Una variabile locale, tuttavia, è nell'ambito di una funzione e sebbene sia possibile accedere a qualsiasi valore di variabile locale che si trova al di fuori del frame di stack della funzione corrente, il sistema tenterà di assicurarsi che questo tipo di comportamento non si verifichi, utilizzando cornici impilate. Questo ci dà un qualche tipo di illusione che la variabile locale abbia come ambito una specifica funzione.


4

Esistono molti modi per implementare le variabili locali da un sistema di runtime linguistico. L'uso di uno stack è una soluzione efficiente comune, utilizzata in molti casi pratici.

Intuitivamente, un puntatore dello stack spviene mantenuto in fase di esecuzione (in un indirizzo fisso o in un registro - è davvero importante). Supponiamo che ogni "push" incrementi il ​​puntatore dello stack.

Al momento della compilazione, il compilatore determina l'indirizzo di ciascuna variabile come sp - Kdove Kè una costante che dipende solo dall'ambito della variabile (quindi può essere calcolata al momento della compilazione).

Nota che qui stiamo usando la parola "pila" in senso lato. Questo stack non è accessibile solo tramite le operazioni push / pop / top, ma è anche accessibile usando sp - K.

Ad esempio, considera questo pseudocodice:

procedure f(int x, int y) {
  print(x,y);    // (1)
  if (...) {
    int z=x+y; // (2)
    print(x,y,z);  // (3)
  }
  print(x,y); // (4)
  return;
}

Quando viene chiamata la procedura, gli argomenti x,ypossono essere passati nello stack. Per semplicità, supponiamo che la convenzione sia xprima chi chiama , poi y.

Quindi, il compilatore al punto (1) può trovare xat sp - 2e yat sp - 1.

Al punto (2), viene portata in campo una nuova variabile. Il compilatore genera il codice che somma x+y, cioè ciò che è indicato da sp - 2e sp - 1, e invia il risultato della somma nello stack.

Al punto (3), zviene stampato. Il compilatore sa che è l'ultima variabile nell'ambito, quindi è indicata da sp - 1. Questo non è più y, poiché è spcambiato. Tuttavia, stampare yil compilatore sa che può trovarlo, in questo ambito, su sp - 2. Allo stesso modo, xora è disponibile all'indirizzo sp - 3.

Al punto (4), usciamo dall'ambito. zè spuntato, e si ytrova di nuovo all'indirizzo sp - 1, ed xè a sp - 2.

Quando torniamo, uno dei due fo il chiamante esce x,ydallo stack.

Pertanto, il calcolo Kper il compilatore si basa sul conteggio di quante variabili rientrano nell'ambito di applicazione. Nel mondo reale, questo è in realtà più complesso poiché non tutte le variabili hanno le stesse dimensioni, quindi il calcolo di Kè leggermente più complesso. A volte, lo stack contiene anche l'indirizzo di ritorno per f, quindi Kdeve "saltare" anche quello. Ma questi sono tecnicismi.

Si noti che, in alcuni linguaggi di programmazione, le cose possono diventare ancora più complesse se si devono gestire funzionalità più complesse. Ad esempio le procedure nidificate richiedono un'analisi molto attenta, poiché Kora deve "saltare" molti indirizzi di ritorno, soprattutto se la procedura nidificata è ricorsiva. Le chiusure / lambda / funzioni anonime richiedono anche una certa cura per gestire le variabili "catturate". Tuttavia, l'esempio sopra dovrebbe illustrare l'idea di base.


3

L'idea più semplice è pensare alle variabili come nomi di correzioni per gli indirizzi in memoria. In effetti, alcuni assemblatori visualizzano il codice macchina in questo modo ("memorizza il valore 5 nell'indirizzo i", dove iè un nome variabile).

Alcuni di questi indirizzi sono "assoluti", come le variabili globali, altri sono "relativi", come le variabili locali. Le variabili (ovvero gli indirizzi) nelle funzioni sono relative ad un posto nello "stack" che è diverso per ogni invocazione di funzione; in questo modo lo stesso nome può riferirsi a diversi oggetti reali e le chiamate circolari alla stessa funzione sono invocazioni indipendenti che lavorano su memoria indipendente.


2

Gli elementi di dati che possono andare in pila vengono messi in pila - Sì! È uno spazio premium. Inoltre, una volta inserito xnello stack e quindi inserito ynello stack, idealmente non possiamo accedervi xfino a quando ynon c'è. Abbiamo bisogno di pop yper accedere x. Li hai fatti bene.

Lo stack non è di variabili, ma di frames

Dove hai sbagliato è lo stack stesso. Sullo stack non sono gli elementi di dati che vengono spinti direttamente. Piuttosto, sullo stack stack-frameviene chiamato qualcosa chiamato . Questo stack frame contiene gli elementi di dati. Sebbene non sia possibile accedere ai frame in profondità nello stack, è possibile accedere al frame superiore e a tutti gli elementi di dati contenuti al suo interno.

Diciamo che i nostri elementi di dati sono raggruppati in due frame stack frame-xe frame-y. Li abbiamo spinti uno dopo l'altro. Ora, purché frame-ysi trovi sopra frame-x, non è possibile accedere idealmente a nessun elemento di dati all'interno frame-x. Solo frame-yè visibile. MA dato che frame-yè visibile, puoi accedere a tutti gli elementi di dati in esso contenuti. L'intero frame è visibile esponendo tutti gli elementi di dati contenuti all'interno.

Fine della risposta Altro (rant) su questi frame

Durante la compilazione, viene creato un elenco di tutte le funzioni del programma. Quindi per ciascuna funzione viene creato un elenco di elementi di dati impilabili . Quindi per ogni funzione stack-frame-templateviene eseguita una a. Questo modello è una struttura di dati che contiene tutte quelle variabili scelte, spazio per i dati di input della funzione, i dati di output ecc. Ora durante il runtime, ogni volta che viene chiamata una funzione, una copia di questa templateviene messa in pila - insieme a tutte le variabili di input e intermedie . Quando questa funzione chiama qualche altra funzione, viene messa in pila una nuova copia di quella funzione stack-frame. Ora fintanto che la funzione è in esecuzione, questo elementi di dati della funzione sono conservati. Una volta terminata quella funzione, viene espulso il suo stack-frame. Adessoquesto stack-frame è attivo e questa funzione può accedere a tutte le sue variabili.

Si noti che la struttura e la composizione di uno stack-frame varia dal linguaggio di programmazione al linguaggio di programmazione. Anche all'interno di una lingua potrebbero esserci sottili differenze nelle diverse implementazioni.


Grazie per aver considerato CS. Sono un programmatore ora un giorno prendendo lezioni di piano :)

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.