Cosa e dove sono lo stack e l'heap?


8105

I libri del linguaggio di programmazione spiegano che i tipi di valore vengono creati nello stack e i tipi di riferimento vengono creati nell'heap , senza spiegare quali sono queste due cose. Non ho letto una spiegazione chiara di questo. Capisco cos'è uno stack . Ma,

  • Dove e cosa sono (fisicamente nella memoria di un vero computer)?
  • In che misura sono controllati dal sistema operativo o dal runtime della lingua?
  • Qual è il loro scopo?
  • Cosa determina la dimensione di ciascuno di essi?
  • Cosa rende uno più veloce?

175
una spiegazione davvero valida può essere trovata qui Qual è la differenza tra una pila e una pila?
Songo

12
Anche (davvero) buono: codeproject.com/Articles/76153/… (la parte stack / heap)
Ben


3
Relativo, vedi Stack Clash . Le soluzioni di Stack Clash hanno influenzato alcuni aspetti delle variabili di sistema e comportamenti simili rlimit_stack. Vedi anche Red Hat, numero 1463241
jww,

3
@mattshane Le definizioni di stack e heap non dipendono dal valore e dai tipi di riferimento. In altre parole, lo stack e l'heap possono essere completamente definiti anche se non sono mai esistiti tipi di valore e di riferimento. Inoltre, quando si comprendono i tipi di valore e di riferimento, lo stack è solo un dettaglio di implementazione. Per Eric Lippert: lo stack è un dettaglio di implementazione, prima parte .
Matteo,

Risposte:


5966

Lo stack è la memoria messa da parte come spazio scratch per un thread di esecuzione. Quando viene chiamata una funzione, un blocco è riservato nella parte superiore dello stack per le variabili locali e alcuni dati di contabilità. Quando quella funzione ritorna, il blocco diventa inutilizzato e può essere usato alla successiva chiamata di una funzione. Lo stack è sempre riservato in un ordine LIFO (last in first out); l'ultimo blocco prenotato è sempre il blocco successivo da liberare. Questo rende davvero semplice tenere traccia dello stack; liberare un blocco dallo stack non è altro che regolare un puntatore.

L'heap è la memoria riservata all'allocazione dinamica. A differenza dello stack, non esiste alcun modello forzato per l'allocazione e la deallocazione dei blocchi dall'heap; puoi allocare un blocco in qualsiasi momento e liberarlo in qualsiasi momento. Ciò rende molto più complesso tenere traccia di quali parti dell'heap sono allocate o libere in un dato momento; ci sono molti allocatori di heap personalizzati disponibili per ottimizzare le prestazioni dell'heap per diversi modelli di utilizzo.

Ogni thread ottiene uno stack, mentre in genere esiste un solo heap per l'applicazione (anche se non è raro avere più heap per diversi tipi di allocazione).

Per rispondere direttamente alle tue domande:

In che misura sono controllati dal sistema operativo o dal runtime della lingua?

Il sistema operativo alloca lo stack per ogni thread a livello di sistema quando viene creato il thread. In genere il sistema operativo viene chiamato dal runtime della lingua per allocare l'heap per l'applicazione.

Qual è il loro scopo?

Lo stack è collegato a un thread, quindi quando il thread esce dallo stack viene recuperato. L'heap viene in genere allocato all'avvio dell'applicazione dal runtime e viene recuperato quando l'applicazione (tecnicamente processo) viene chiusa.

Cosa determina la dimensione di ciascuno di essi?

La dimensione dello stack viene impostata quando viene creato un thread. La dimensione dell'heap è impostata all'avvio dell'applicazione, ma può aumentare in base allo spazio necessario (l'allocatore richiede più memoria dal sistema operativo).

Cosa rende uno più veloce?

Lo stack è più veloce perché il modello di accesso rende banale l'allocazione e la deallocazione della memoria da esso (un puntatore / intero viene semplicemente incrementato o decrementato), mentre l'heap ha una contabilità molto più complessa coinvolta in un'allocazione o deallocazione. Inoltre, ogni byte nello stack tende a essere riutilizzato molto frequentemente, il che significa che tende a essere mappato nella cache del processore, rendendolo molto veloce. Un altro risultato positivo per l'heap è che l'heap, essendo principalmente una risorsa globale, deve essere in genere multi-threading sicuro, vale a dire che ogni allocazione e deallocazione devono essere - in genere - sincronizzate con "tutti" gli altri accessi heap nel programma.

Una chiara dimostrazione:
Fonte immagine: vikashazrati.wordpress.com


74
Buona risposta - ma penso che dovresti aggiungere che mentre lo stack è allocato dal sistema operativo all'avvio del processo (presupponendo l'esistenza di un sistema operativo), viene mantenuto in linea dal programma. Questo è un altro motivo per cui lo stack è più veloce: le operazioni push e pop sono in genere un'istruzione di macchina e le macchine moderne possono eseguirne almeno 3 in un ciclo, mentre l'allocazione o la liberazione dell'heap implica la chiamata al codice del sistema operativo.
sqykly

276
Sono davvero confuso dal diagramma alla fine. Pensavo di averlo ottenuto fino a quando non ho visto quell'immagine.
Sina Madani,

10
@Anarelle il processore esegue le istruzioni con o senza sistema operativo. Un esempio vicino al mio cuore è il SNES, che non aveva chiamate API, nessun sistema operativo come lo conosciamo oggi - ma aveva uno stack. L'allocazione su uno stack è addizione e sottrazione su questi sistemi e questo va bene per le variabili distrutte quando vengono saltate tornando dalla funzione che le ha create, ma costruiscile per, diciamo, un costruttore, di cui il risultato non può essere gettato via. Per questo abbiamo bisogno dell'heap, che non è legato alla chiamata e al ritorno. La maggior parte dei sistemi operativi ha un heap di API, nessun motivo per farlo da soli
sqykly

2
"stack è la memoria messa da parte come spazio scratch". Freddo. Ma dove si trova "messo da parte" in termini di struttura della memoria Java ?? Memoria heap / Memoria non heap / Altro (struttura della memoria Java come da betsol.com/2017/06/… )
Jatin Shashoo

4
Il runtime Java @JatinShashoo, come interprete bytecode, aggiunge un ulteriore livello di virtualizzazione, quindi quello a cui si fa riferimento è solo il punto di vista dell'applicazione Java. Dal punto di vista del sistema operativo tutto ciò che è solo un heap, in cui il processo di runtime Java alloca parte del suo spazio come memoria "non heap" per il bytecode elaborato. Il resto dell'heap a livello di sistema operativo viene utilizzato come heap a livello di applicazione, in cui sono archiviati i dati dell'oggetto.
Kbec,

2350

Pila:

  • Memorizzato nella RAM del computer proprio come l'heap.
  • Le variabili create nello stack non rientrano nell'ambito di applicazione e vengono automaticamente allocate.
  • Molto più veloce da allocare rispetto alle variabili dell'heap.
  • Implementato con una struttura dati stack reale.
  • Memorizza i dati locali, gli indirizzi di ritorno, utilizzati per il passaggio dei parametri.
  • Può avere un overflow dello stack quando viene utilizzato troppo dello stack (principalmente da ricorsione infinita o troppo profonda, allocazioni molto grandi).
  • I dati creati nello stack possono essere utilizzati senza puntatori.
  • Utilizzeresti lo stack se sapessi esattamente quanti dati devi allocare prima del tempo di compilazione e non sono troppo grandi.
  • Di solito ha una dimensione massima già determinata all'avvio del programma.

Mucchio:

  • Memorizzato nella RAM del computer proprio come lo stack.
  • In C ++, le variabili sull'heap devono essere distrutte manualmente e non vanno mai al di fuori dell'ambito. I dati si libera con delete, delete[]o free.
  • Più lento da allocare rispetto alle variabili nello stack.
  • Utilizzato su richiesta per allocare un blocco di dati per l'utilizzo da parte del programma.
  • Può avere frammentazione quando ci sono molte allocazioni e deallocazioni.
  • In C ++ o C, i dati creati sull'heap saranno indicati da puntatori e allocati con newo mallocrispettivamente.
  • Possono verificarsi errori di allocazione se si richiede di allocare un buffer troppo grande.
  • Utilizzeresti l'heap se non sai esattamente quanti dati ti occorrono in fase di esecuzione o se devi allocare molti dati.
  • Responsabile di perdite di memoria.

Esempio:

int foo()
{
  char *pBuffer; //<--nothing allocated yet (excluding the pointer itself, which is allocated here on the stack).
  bool b = true; // Allocated on the stack.
  if(b)
  {
    //Create 500 bytes on the stack
    char buffer[500];

    //Create 500 bytes on the heap
    pBuffer = new char[500];

   }//<-- buffer is deallocated here, pBuffer is not
}//<--- oops there's a memory leak, I should have called delete[] pBuffer;

31
Il puntatore pBuffer e il valore di b si trovano nello stack e sono probabilmente allocati all'ingresso della funzione. A seconda del compilatore, il buffer può essere allocato anche all'ingresso della funzione.
Andy,

36
È un'idea sbagliata comune che la Clingua, come definita dallo C99standard linguistico (disponibile su open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf ), richieda uno "stack". In effetti, la parola 'stack' non appare nemmeno nello standard. Ciò risponde alle affermazioni che l' Cutilizzo dello stack wrt / to è vero in generale, ma non è in alcun modo richiesto dalla lingua. Vedi knosof.co.uk/cbook/cbook.html per maggiori informazioni, e in particolare come Cviene implementato su architetture a palla dispari come en.wikipedia.org/wiki/Burroughs_large_systems
johne,

55
@Brian Dovresti spiegare perché il buffer [] e il puntatore pBuffer vengono creati nello stack e perché i dati di pBuffer vengono creati nell'heap. Penso che alcuni ppl potrebbero essere confusi dalla tua risposta in quanto potrebbero pensare che il programma stia specificatamente istruendo che la memoria sia allocata sullo stack contro l'heap, ma non è così. È perché Buffer è un tipo di valore mentre pBuffer è un tipo di riferimento?
Howiecamp,

9
@Remover: nessun puntatore contiene un indirizzo e può indicare ugualmente qualcosa sull'heap o sullo stack. new, malloc e alcune altre funzioni simili a malloc si allocano sull'heap e restituiscono l'indirizzo della memoria allocata. Perché dovresti allocare sull'heap? In modo che la tua memoria non esca dal campo di applicazione e venga rilasciata fino a quando non lo desideri.
Brian R. Bondy,

35
"Responsabile delle perdite di memoria" - Gli heap non sono responsabili delle perdite di memoria! Sono programmatori / programmatori pigri / dimenticanti / ex-java che non se ne fregano!
Laz

1370

Il punto più importante è che heap e stack sono termini generici per i modi in cui la memoria può essere allocata. Possono essere implementati in molti modi diversi e i termini si applicano ai concetti di base.

  • In una pila di oggetti, gli oggetti si trovano uno sopra l'altro nell'ordine in cui sono stati posizionati lì, e puoi rimuovere solo quello superiore (senza rovesciare il tutto).

    Pila come una pila di fogli

    La semplicità di uno stack è che non è necessario mantenere una tabella contenente un record di ogni sezione della memoria allocata; le uniche informazioni sullo stato necessarie sono un singolo puntatore alla fine dello stack. Per allocare e de-allocare, è sufficiente incrementare e decrementare quel singolo puntatore. Nota: a volte uno stack può essere implementato per iniziare nella parte superiore di una sezione della memoria e estendersi verso il basso anziché crescere verso l'alto.

  • In un heap, non esiste un ordine particolare per il modo in cui gli oggetti vengono posizionati. Puoi accedere e rimuovere gli elementi in qualsiasi ordine perché non esiste un elemento "top" chiaro.

    Mucchio come un mucchio di liquirizia allsorts

    L'allocazione dell'heap richiede il mantenimento di una registrazione completa di ciò che la memoria è allocata e di ciò che non lo è, nonché una certa manutenzione ambientale per ridurre la frammentazione, trovare segmenti di memoria contigui abbastanza grandi da adattarsi alle dimensioni richieste e così via. La memoria può essere deallocata in qualsiasi momento lasciando spazio libero. A volte un allocatore di memoria eseguirà attività di manutenzione come la deframmentazione della memoria spostando la memoria allocata o la garbage collection, identificando in fase di esecuzione quando la memoria non è più nell'ambito e distribuendola.

Queste immagini dovrebbero fare un buon lavoro nel descrivere i due modi di allocare e liberare memoria in uno stack e in un heap. Yum!

  • In che misura sono controllati dal sistema operativo o dal runtime della lingua?

    Come accennato, heap e stack sono termini generali e possono essere implementati in molti modi. I programmi per computer in genere hanno uno stack chiamato stack di chiamate , che memorizza le informazioni rilevanti per la funzione attuale, come un puntatore a qualsiasi funzione è stato chiamato da, e le eventuali variabili locali. Poiché le funzioni chiamano altre funzioni e poi ritornano, lo stack cresce e si restringe per contenere le informazioni dalle funzioni più in basso nello stack di chiamate. Un programma in realtà non ha il controllo di runtime su di esso; è determinato dal linguaggio di programmazione, dal sistema operativo e persino dall'architettura del sistema.

    Un heap è un termine generale utilizzato per qualsiasi memoria allocata in modo dinamico e casuale; cioè fuori servizio. La memoria è in genere allocata dal sistema operativo, con l'applicazione che chiama le funzioni API per eseguire questa allocazione. C'è un bel po 'di overhead richiesto nella gestione della memoria allocata in modo dinamico, che di solito è gestita dal codice di runtime del linguaggio di programmazione o dell'ambiente utilizzato.

  • Qual è il loro scopo?

    Lo stack di chiamate è un concetto di livello così basso che non si riferisce a "scope" nel senso della programmazione. Se disassembli un po 'di codice, vedrai riferimenti relativi allo stile del puntatore a porzioni dello stack, ma per quanto riguarda una lingua di livello superiore, la lingua impone le proprie regole di ambito. Un aspetto importante di uno stack, tuttavia, è che una volta che una funzione ritorna, qualsiasi cosa locale a quella funzione viene immediatamente liberata dallo stack. Funziona come ti aspetteresti che funzioni, dato come funzionano i tuoi linguaggi di programmazione. In un heap, è anche difficile da definire. L'ambito è qualunque cosa sia esposta dal sistema operativo, ma il tuo linguaggio di programmazione probabilmente aggiunge le sue regole su ciò che un "ambito" è nella tua applicazione. L'architettura del processore e il sistema operativo utilizzano l'indirizzamento virtuale, che il processore traduce in indirizzi fisici e che ci sono errori di pagina, ecc. Tengono traccia di quali pagine appartengono a quali applicazioni. Non devi mai preoccuparti di questo, però, perché usi semplicemente il metodo utilizzato dal tuo linguaggio di programmazione per allocare e liberare memoria e controllare gli errori (se l'allocazione / liberazione fallisce per qualsiasi motivo).

  • Cosa determina la dimensione di ciascuno di essi?

    Ancora una volta, dipende dal linguaggio, dal compilatore, dal sistema operativo e dall'architettura. Uno stack è generalmente pre-allocato, perché per definizione deve essere memoria contigua. Il compilatore del linguaggio o il sistema operativo determinano le sue dimensioni. Non memorizzi enormi blocchi di dati nello stack, quindi sarà abbastanza grande da non essere mai utilizzato completamente, tranne nei casi di ricorsione infinita indesiderata (quindi, "overflow dello stack") o altre decisioni di programmazione insolite.

    Un heap è un termine generale per tutto ciò che può essere allocato in modo dinamico. A seconda del modo in cui lo guardi, cambia costantemente dimensione. Nei moderni processori e sistemi operativi il modo esatto in cui funziona è comunque molto astratto, quindi normalmente non devi preoccuparti molto di come funziona in profondità, tranne che (nelle lingue in cui ti consente) non devi usare la memoria che non hai ancora allocato o memoria che hai liberato.

  • Cosa rende uno più veloce?

    Lo stack è più veloce perché tutta la memoria libera è sempre contigua. Non è necessario mantenere un elenco di tutti i segmenti di memoria libera, solo un singolo puntatore all'attuale cima dello stack. I compilatori di solito memorizzano questo puntatore in un registro speciale e veloce per questo scopo. Inoltre, le operazioni successive su uno stack sono generalmente concentrate all'interno di aree di memoria molto vicine, il che a un livello molto basso è buono per l'ottimizzazione da parte delle cache on-die del processore.


20
David Non sono d'accordo sul fatto che si tratti di una buona immagine o che "stack push-down" sia un buon termine per illustrare il concetto. Quando aggiungi qualcosa a una pila, gli altri contenuti della pila non vengono spinti verso il basso, rimangono dove sono.
thomasrutter,

8
Questa risposta include un grosso errore. Le variabili statiche non sono allocate nello stack. Vedi la mia risposta [link] stackoverflow.com/a/13326916/1763801 per chiarimenti. stai equiparando le variabili "automatiche" alle variabili "statiche", ma non sono affatto uguali
davec

13
In particolare, si dice che "variabili locali allocate staticamente" sono allocate nello stack. In realtà sono allocati nel segmento di dati. Nello stack vengono allocate solo le variabili allocate automaticamente (che include la maggior parte ma non tutte le variabili locali e anche cose come i parametri di funzione passati per valore anziché per riferimento).
davec

9
Ho appena capito che hai ragione: in C, l' allocazione statica è una cosa separata piuttosto che un termine per tutto ciò che non è dinamico . Ho modificato la mia risposta, grazie.
Thomas Thomas

5
Non è solo C. Java, Pascal, Python e molti altri hanno tutti le nozioni di allocazione statica contro automatica contro dinamica. Dire "allocazione statica" significa la stessa cosa praticamente ovunque. In nessun linguaggio allocazione statica significa "non dinamico". Volete il termine allocazione "automatica" per ciò che state descrivendo (cioè le cose in pila).
davec,

727

(Ho spostato questa risposta da un'altra domanda che era più o meno una copia di questa.)

La risposta alla tua domanda è specifica dell'implementazione e può variare a seconda dei compilatori e delle architetture dei processori. Tuttavia, ecco una spiegazione semplificata.

  • Sia lo stack che l'heap sono aree di memoria allocate dal sistema operativo sottostante (spesso memoria virtuale mappata su memoria fisica su richiesta).
  • In un ambiente multi-thread ogni thread avrà il proprio stack completamente indipendente ma condividerà l'heap. L'accesso simultaneo deve essere controllato nell'heap e non è possibile nello stack.

Il mucchio

  • L'heap contiene un elenco collegato di blocchi usati e liberi. Le nuove allocazioni sull'heap (da newo malloc) vengono soddisfatte creando un blocco adatto da uno dei blocchi liberi. Ciò richiede l'aggiornamento dell'elenco di blocchi sull'heap. Queste meta informazioni sui blocchi nell'heap sono anche memorizzate nell'heap spesso in una piccola area proprio di fronte a ogni blocco.
  • Man mano che l'heap cresce, i nuovi blocchi vengono spesso allocati da indirizzi inferiori a indirizzi più alti. Quindi puoi pensare all'heap come a un mucchio di blocchi di memoria che aumenta di dimensioni man mano che la memoria viene allocata. Se l'heap è troppo piccolo per un'allocazione, è spesso possibile aumentare le dimensioni acquisendo più memoria dal sistema operativo sottostante.
  • Allocare e deallocare molti piccoli blocchi può lasciare l'heap in uno stato in cui ci sono molti piccoli blocchi liberi intervallati tra i blocchi usati. Una richiesta per allocare un blocco di grandi dimensioni potrebbe non riuscire perché nessuno dei blocchi liberi è abbastanza grande da soddisfare la richiesta di allocazione anche se la dimensione combinata dei blocchi liberi può essere abbastanza grande. Questo si chiama frammentazione dell'heap .
  • Quando un blocco usato adiacente a un blocco libero viene deallocato, il nuovo blocco libero può essere unito al blocco libero adiacente per creare un blocco libero più grande riducendo efficacemente la frammentazione dell'heap.

Il mucchio

Lo stack

  • Lo stack funziona spesso in tandem con un registro speciale sulla CPU chiamato puntatore dello stack . Inizialmente il puntatore dello stack punta in cima allo stack (l'indirizzo più alto nello stack).
  • La CPU ha istruzioni speciali per spingere i valori nello stack e rimuoverli dallo stack. Ogni push memorizza il valore nella posizione corrente del puntatore dello stack e diminuisce il puntatore dello stack. Un pop recupera il valore a cui punta il puntatore dello stack e quindi aumenta il puntatore dello stack (non essere confuso dal fatto che l' aggiunta di un valore allo stack diminuisce il puntatore dello stack e la rimozione di un valore lo aumenta . Ricorda che lo stack cresce fino a il fondo). I valori memorizzati e recuperati sono i valori dei registri della CPU.
  • Quando viene chiamata una funzione, la CPU utilizza istruzioni speciali che inviano il puntatore dell'istruzione corrente , ovvero l'indirizzo del codice in esecuzione sullo stack. La CPU passa quindi alla funzione impostando il puntatore dell'istruzione sull'indirizzo della funzione chiamata. Successivamente, quando la funzione ritorna, il vecchio puntatore all'istruzione viene estratto dallo stack e l'esecuzione riprende al codice subito dopo la chiamata alla funzione.
  • Quando viene immessa una funzione, il puntatore dello stack viene ridotto per allocare più spazio nello stack per le variabili locali (automatiche). Se la funzione ha una variabile locale a 32 bit, quattro byte vengono messi da parte nello stack. Quando la funzione ritorna, il puntatore dello stack viene spostato indietro per liberare l'area allocata.
  • Se una funzione ha parametri, questi vengono inseriti nello stack prima della chiamata alla funzione. Il codice nella funzione è quindi in grado di spostarsi nello stack dal puntatore dello stack corrente per individuare questi valori.
  • Le chiamate della funzione di nidificazione funzionano come un fascino. Ogni nuova chiamata assegnerà i parametri delle funzioni, l'indirizzo di ritorno e lo spazio per le variabili locali e questi record di attivazione possono essere impilati per le chiamate nidificate e si svolgeranno nel modo corretto quando le funzioni ritornano.
  • Poiché lo stack è un blocco limitato di memoria, è possibile causare un overflow dello stack chiamando troppe funzioni nidificate e / o allocando troppo spazio per le variabili locali. Spesso l'area di memoria utilizzata per lo stack è impostata in modo tale che la scrittura sotto il fondo (l'indirizzo più basso) dello stack attiverà una trap o un'eccezione nella CPU. Questa condizione eccezionale può quindi essere rilevata dal runtime e convertita in una sorta di eccezione di overflow dello stack.

Lo stack

È possibile allocare una funzione sull'heap anziché su uno stack?

No, i record di attivazione per funzioni (ovvero variabili locali o automatiche) sono allocati nello stack che viene utilizzato non solo per memorizzare queste variabili, ma anche per tenere traccia delle chiamate di funzione nidificate.

Come viene gestito l'heap dipende davvero dall'ambiente di runtime. C usa malloce C ++ usa new, ma molte altre lingue hanno la garbage collection.

Tuttavia, lo stack è una funzionalità di livello più basso strettamente legata all'architettura del processore. Crescere l'heap quando non c'è abbastanza spazio non è troppo difficile poiché può essere implementato nella chiamata della libreria che gestisce l'heap. Tuttavia, aumentare lo stack è spesso impossibile poiché lo overflow dello stack viene scoperto solo quando è troppo tardi; e la chiusura del thread di esecuzione è l'unica opzione praticabile.


35
@Martin: un'ottima risposta / spiegazione rispetto alla risposta più astratta accettata. Un programma di assemblaggio di esempio che mostra i puntatori / registri dello stack utilizzati per le chiamate di una funzione vis sarebbe più illustrativo.
Bikal Lem,

3
Ogni tipo di riferimento è una composizione di tipi di valore (int, stringa ecc.). Come si dice, quei tipi di valore sono memorizzati in pila rispetto a come funziona quando fanno parte del tipo di riferimento.
NPS,

15
Questa risposta è stata la migliore a mio avviso, perché mi ha aiutato a capire cosa sia realmente un'istruzione return e in che modo si riferisca a questo "indirizzo di ritorno" che mi capita di incontrare di tanto in tanto, cosa significa inserire una funzione nello stack, e perché le funzioni sono inserite in pile. Bella risposta!
Alex

3
Questo è il migliore secondo me, vale a dire per menzionare che l'heap / stack è molto specifico per l'implementazione. Le altre risposte presuppongono molte cose sulla lingua e sull'ambiente / sistema operativo. +1
Qix - MONICA È STATA MISTREATA il

2
Cosa vuoi dire "Il codice nella funzione è quindi in grado di spostarsi nello stack dal puntatore dello stack corrente per individuare questi valori." ? Puoi approfondire questo per favore?
Koray Tugay,

404

Nel seguente codice C #

public void Method1()
{
    int i = 4;
    int y = 2;
    class1 cls1 = new class1();
}

Ecco come viene gestita la memoria

Immagine delle variabili in pila

Local Variablesche deve durare solo finché l'invocazione della funzione va nello stack. L'heap viene utilizzato per le variabili di cui non conosciamo in anticipo la durata ma ci aspettiamo che durino un po '. Nella maggior parte delle lingue è fondamentale sapere al momento della compilazione quanto è grande una variabile se vogliamo salvarla nello stack.

Gli oggetti (che variano di dimensione man mano che li aggiorniamo) vanno nell'heap perché al momento della creazione non sappiamo quanto dureranno. In molte lingue, l'heap viene raccolto in modo inutile per trovare oggetti (come l'oggetto cls1) che non hanno più riferimenti.

In Java, la maggior parte degli oggetti va direttamente nell'heap. In linguaggi come C / C ++, le strutture e le classi possono spesso rimanere in pila quando non si hanno a che fare con i puntatori.

Ulteriori informazioni si possono trovare qui:

La differenza tra allocazione memoria stack e heap «timmurphy.org

e qui:

Creazione di oggetti in pila e heap

Questo articolo è la fonte dell'immagine sopra: sei importanti concetti .NET: Stack, heap, tipi di valore, tipi di riferimento, boxe e unboxing - CodeProject

ma tieni presente che potrebbe contenere alcune imprecisioni.


15
Questo non è corretto i e cls non sono variabili "statiche". sono chiamate variabili "locali" o "automatiche". È una distinzione molto importante. Vedere [link] stackoverflow.com/a/13326916/1763801 chiarimenti
davec

9
Non ho detto che fossero variabili statiche . Ho detto che int e cls1 sono oggetti statici . La loro memoria è allocata staticamente e quindi vanno in pila. Ciò è in contrasto con un oggetto che richiede un'allocazione dinamica della memoria che quindi va sull'heap.
Snowcrash,

12
Cito "Articoli statici ... vai in pila". Questo è semplicemente sbagliato. Gli oggetti statici vanno nel segmento dati, gli oggetti automatici vanno in pila.
davec

14
Inoltre, chiunque abbia scritto l'articolo codeproject non sa di cosa sta parlando. Ad esempio, afferma che "quelli primitivi hanno bisogno di memoria di tipo statico" che è completamente falso. Nulla ti impedisce di allocare dinamicamente le primitive nell'heap, basta scrivere qualcosa come "int array [] = new int [num]" e voilà, primitive allocate dinamicamente in .NET. Questa è solo una delle numerose inesattezze.
davec

8
Ho modificato il tuo post perché hai commesso gravi errori tecnici su ciò che va in pila e heap.
Tom Leys,

209

Lo stack Quando chiamate una funzione, gli argomenti di quella funzione più qualche altro overhead vengono messi nello stack. Alcune informazioni (come dove andare al ritorno) sono anche memorizzate lì. Quando dichiari una variabile all'interno della tua funzione, anche quella variabile viene allocata nello stack.

La deallocazione dello stack è piuttosto semplice perché si sposta sempre nell'ordine inverso in cui si alloca. Le cose dello stack vengono aggiunte quando si inseriscono le funzioni, i dati corrispondenti vengono rimossi quando si esce. Ciò significa che tendi a rimanere all'interno di una piccola area dello stack a meno che non chiami molte funzioni che chiamano molte altre funzioni (o crei una soluzione ricorsiva).

L'heap L'heap è un nome generico per il quale metti al volo i dati che crei. Se non sai quante astronavi creerà il tuo programma, è probabile che tu usi il nuovo operatore (o malloc o equivalente) per creare ogni astronave. Questa allocazione rimarrà per un po ', quindi è probabile che libereremo le cose in un ordine diverso da come le abbiamo create.

Quindi, l'heap è molto più complesso, perché finiscono per esserci regioni di memoria che non vengono utilizzate interlacciate con blocchi che sono - la memoria viene frammentata. Trovare memoria libera delle dimensioni necessarie è un problema difficile. Questo è il motivo per cui l'heap dovrebbe essere evitato (sebbene sia ancora spesso usato).

Implementazione L' implementazione sia dello stack che dell'heap generalmente dipende dal runtime / sistema operativo. Spesso i giochi e le altre applicazioni critiche per le prestazioni creano le proprie soluzioni di memoria che catturano una grande quantità di memoria dall'heap e quindi la distribuiscono internamente per evitare di fare affidamento sul sistema operativo per la memoria.

Ciò è pratico solo se l'utilizzo della memoria è abbastanza diverso dalla norma, ad esempio per i giochi in cui si carica un livello in un'operazione enorme e si può eliminare l'intero lotto in un'altra operazione enorme.

Posizione fisica nella memoria Questo è meno rilevante di quanto si pensi a causa di una tecnologia chiamata Memoria Virtuale che fa pensare al tuo programma di avere accesso a un determinato indirizzo in cui i dati fisici sono altrove (anche sul disco rigido!). Gli indirizzi che ottieni per lo stack sono in ordine crescente man mano che la struttura delle chiamate diventa più profonda. Gli indirizzi per l'heap sono imprevedibili (cioè specifici per l'implementazione) e francamente non importanti.


16
Una raccomandazione per evitare di usare l'heap è piuttosto forte. I sistemi moderni hanno buoni gestori di heap e i moderni linguaggi dinamici usano ampiamente l'heap (senza che il programmatore si preoccupi davvero). Direi usare l'heap, ma con un allocatore manuale, non dimenticare di liberare!
Greg Hewgill,

2
Se puoi usare la pila o il mucchio, usa la pila. Se non puoi usare la pila, davvero nessuna scelta. Uso entrambi molto, e ovviamente usando heap std :: vector o simili. Per un principiante, eviti l'heap perché lo stack è semplicemente così facile !!
Tom Leys,

Se la tua lingua non implementa la garbage collection, i puntatori intelligenti (oggetti allocati separatamente che avvolgono un puntatore che fanno il conteggio dei riferimenti per blocchi di memoria allocati dinamicamente) sono strettamente correlati alla garbage collection e sono un modo decente di gestire l'heap in modo sicuro e modo senza perdite. Sono implementati in vari framework, ma non sono poi così difficili da implementare anche per i tuoi programmi.
BenPen,

1
"Questo è il motivo per cui l'heap dovrebbe essere evitato (anche se è ancora spesso usato)." Non sono sicuro di cosa significhi praticamente ciò, soprattutto perché la memoria è gestita in modo diverso in molte lingue di alto livello. Dato che questa domanda è taggata dal linguaggio agnostico, direi che questo particolare commento / riga è mal posizionato e non applicabile.
LintfordPickle,

2
Buon punto @JonnoHampson - Mentre fai un punto valido, direi che se stai lavorando in un "linguaggio di alto livello" con un GC probabilmente non ti preoccupi affatto dei meccanismi di allocazione della memoria - e quindi non importa anche quali sono lo stack e l'heap.
Tom Leys,

194

Per chiarire, questa risposta ha informazioni errate ( Thomas ha corretto la sua risposta dopo i commenti, figo :)). Altre risposte evitano semplicemente di spiegare cosa significhi allocazione statica. Spiegherò quindi le tre principali forme di allocazione e il modo in cui di solito si collegano all'heap, allo stack e al segmento di dati di seguito. Mostrerò anche alcuni esempi in C / C ++ e Python per aiutare le persone a capire.

Le variabili "statiche" (AKA allocate staticamente) non sono allocate nello stack. Non dare per scontato che - molte persone lo fanno solo perché "statico" suona molto come "stack". In realtà non esistono né nello stack né nell'heap. Fanno parte di quello che viene chiamato il segmento di dati .

Tuttavia, è generalmente meglio considerare " scope " e " life " piuttosto che "stack" e "heap".

L'ambito si riferisce a quali parti del codice possono accedere a una variabile. Generalmente pensiamo all'ambito locale (accessibile solo dalla funzione corrente) rispetto all'ambito globale (accessibile ovunque) anche se l'ambito può diventare molto più complesso.

Lifetime si riferisce a quando una variabile viene allocata e deallocata durante l'esecuzione del programma. Di solito pensiamo all'allocazione statica (la variabile persisterà per tutta la durata del programma, rendendola utile per memorizzare le stesse informazioni attraverso più chiamate di funzione) rispetto all'allocazione automatica (la variabile persiste solo durante una singola chiamata a una funzione, rendendola utile per memorizzazione delle informazioni utilizzate solo durante la funzione e che possono essere eliminate una volta terminato) rispetto all'allocazione dinamica (variabili la cui durata è definita in fase di esecuzione, anziché il tempo di compilazione come statico o automatico).

Sebbene la maggior parte dei compilatori e degli interpreti implementino questo comportamento in modo simile in termini di utilizzo di stack, heap, ecc., Un compilatore può talvolta infrangere queste convenzioni se lo desidera purché il comportamento sia corretto. Ad esempio, a causa dell'ottimizzazione, una variabile locale può esistere solo in un registro o essere rimossa del tutto, anche se la maggior parte delle variabili locali esiste nello stack. Come è stato sottolineato in alcuni commenti, sei libero di implementare un compilatore che non utilizza nemmeno uno stack o un heap, ma invece alcuni altri meccanismi di archiviazione (fatto raramente, poiché stack e heap sono ottimi per questo).

Fornirò un semplice codice C annotato per illustrare tutto questo. Il modo migliore per imparare è eseguire un programma con un debugger e osservarne il comportamento. Se preferisci leggere Python, salta alla fine della risposta :)

// Statically allocated in the data segment when the program/DLL is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in the code
int someGlobalVariable;

// Statically allocated in the data segment when the program is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in this particular code file
static int someStaticVariable;

// "someArgument" is allocated on the stack each time MyFunction is called
// "someArgument" is deallocated when MyFunction returns
// scope - can be accessed only within MyFunction()
void MyFunction(int someArgument) {

    // Statically allocated in the data segment when the program is first loaded
    // Deallocated when the program/DLL exits
    // scope - can be accessed only within MyFunction()
    static int someLocalStaticVariable;

    // Allocated on the stack each time MyFunction is called
    // Deallocated when MyFunction returns
    // scope - can be accessed only within MyFunction()
    int someLocalVariable;

    // A *pointer* is allocated on the stack each time MyFunction is called
    // This pointer is deallocated when MyFunction returns
    // scope - the pointer can be accessed only within MyFunction()
    int* someDynamicVariable;

    // This line causes space for an integer to be allocated in the heap
    // when this line is executed. Note this is not at the beginning of
    // the call to MyFunction(), like the automatic variables
    // scope - only code within MyFunction() can access this space
    // *through this particular variable*.
    // However, if you pass the address somewhere else, that code
    // can access it too
    someDynamicVariable = new int;


    // This line deallocates the space for the integer in the heap.
    // If we did not write it, the memory would be "leaked".
    // Note a fundamental difference between the stack and heap
    // the heap must be managed. The stack is managed for us.
    delete someDynamicVariable;

    // In other cases, instead of deallocating this heap space you
    // might store the address somewhere more permanent to use later.
    // Some languages even take care of deallocation for you... but
    // always it needs to be taken care of at runtime by some mechanism.

    // When the function returns, someArgument, someLocalVariable
    // and the pointer someDynamicVariable are deallocated.
    // The space pointed to by someDynamicVariable was already
    // deallocated prior to returning.
    return;
}

// Note that someGlobalVariable, someStaticVariable and
// someLocalStaticVariable continue to exist, and are not
// deallocated until the program exits.

Un esempio particolarmente toccante del perché è importante distinguere tra durata e ambito è che una variabile può avere ambito locale ma durata statica, ad esempio "someLocalStaticVariable" nell'esempio di codice sopra. Tali variabili possono rendere le nostre abitudini di denominazione comuni ma informali molto confuse. Ad esempio, quando diciamo " locale " di solito intendiamo " variabile allocata automaticamente con ambito locale " e quando diciamo globale intendiamo generalmente " variabile allocata staticamente con ambito globale ". Sfortunatamente quando si tratta di cose come " variabili allocate staticamente nell'ambito dei file " molte persone dicono solo ... " eh ??? ".

Alcune delle scelte di sintassi in C / C ++ esacerbano questo problema, ad esempio molte persone pensano che le variabili globali non siano "statiche" a causa della sintassi mostrata di seguito.

int var1; // Has global scope and static allocation
static int var2; // Has file scope and static allocation

int main() {return 0;}

Si noti che inserendo la parola chiave "statica" nella dichiarazione precedente si impedisce a var2 di avere un ambito globale. Tuttavia, var1 globale ha allocazione statica. Questo non è intuitivo! Per questo motivo, provo a non usare mai la parola "statico" quando descrivo l'ambito, e invece dico qualcosa come "file" o "ambito limitato". Tuttavia, molte persone usano la frase "statico" o "ambito statico" per descrivere una variabile a cui è possibile accedere solo da un file di codice. Nel contesto della durata, "statico" significa sempre che la variabile viene allocata all'avvio del programma e dislocata quando il programma termina.

Alcune persone pensano a questi concetti come specifici per C / C ++. Non sono. Ad esempio, il seguente esempio di Python illustra tutti e tre i tipi di allocazione (ci sono alcune sottili differenze possibili nei linguaggi interpretati che non tratterò qui).

from datetime import datetime

class Animal:
    _FavoriteFood = 'Undefined' # _FavoriteFood is statically allocated

    def PetAnimal(self):
        curTime = datetime.time(datetime.now()) # curTime is automatically allocatedion
        print("Thank you for petting me. But it's " + str(curTime) + ", you should feed me. My favorite food is " + self._FavoriteFood)

class Cat(Animal):
    _FavoriteFood = 'tuna' # Note since we override, Cat class has its own statically allocated _FavoriteFood variable, different from Animal's

class Dog(Animal):
    _FavoriteFood = 'steak' # Likewise, the Dog class gets its own static variable. Important to note - this one static variable is shared among all instances of Dog, hence it is not dynamic!


if __name__ == "__main__":
    whiskers = Cat() # Dynamically allocated
    fido = Dog() # Dynamically allocated
    rinTinTin = Dog() # Dynamically allocated

    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

    Dog._FavoriteFood = 'milkbones'
    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

# Output is:
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is milkbones
# Thank you for petting me. But it's 13:05:02.256000, you should feed me. My favorite food is milkbones

Vorrei fare riferimento a una variabile statica dichiarata all'interno di una funzione come avente solo accessibilità locale , ma generalmente non utilizzerei il termine "ambito" con essa. Inoltre, vale la pena notare che l'unico aspetto stack / heap con cui le lingue hanno essenzialmente una flessibilità pari a zero: un linguaggio che salva il contesto di esecuzione su uno stack non può usare quello stesso stack per contenere elementi che dovranno sopravvivere ai contesti in cui sono creati . Alcune lingue come PostScripthanno più stack, ma hanno un "heap" che si comporta più come uno stack.
supercat

@supercat Questo ha senso. Ho definito l'ambito come "quali parti del codice possono accedere a una variabile" (e ritengo che questa sia la definizione più standard), quindi penso che siamo d'accordo :)
davec

Considererei lo "scopo" di una variabile come limitato dal tempo e dallo spazio. Per mantenere il valore è necessaria una variabile nell'ambito di un oggetto di classe finché l'oggetto esiste. È necessario che una variabile all'interno di un ambito del contesto di esecuzione mantenga il suo valore finché l'esecuzione rimane in quel contesto. Una dichiarazione di variabile statica crea un identificatore il cui ambito è limitato al blocco corrente, che è collegato a una variabile il cui ambito è illimitato.
supercat

@supercat Questo è il motivo per cui uso la parola vita, che è il modo in cui definisco ciò che chiami ambito temporale. Riduce la necessità di sovraccaricare la parola "portata" con così tanti significati. Per quanto posso dire, non sembra esserci un consenso totale sulle definizioni esatte, anche tra le fonti canoniche. La mia terminologia deriva in parte da K&R e in parte dall'uso prevalente nel primo dipartimento CS in cui ho studiato / insegnato. Sempre bello ascoltare un'altra visione informata.
davec,

1
stai scherzando. puoi davvero definire una variabile statica all'interno di una funzione?
Zaeem Sattar

168

Altri hanno risposto abbastanza bene ai tratti, quindi aggiungerò alcuni dettagli.

  1. Stack e heap non devono essere singolari. Una situazione comune in cui hai più di uno stack è se hai più di un thread in un processo. In questo caso ogni thread ha il suo stack. Puoi anche avere più di un heap, ad esempio alcune configurazioni DLL possono comportare l'allocazione di DLL diverse da heap diversi, motivo per cui è generalmente una cattiva idea rilasciare memoria allocata da una libreria diversa.

  2. In C puoi ottenere il vantaggio dell'allocazione a lunghezza variabile attraverso l'uso di alloca , che alloca sullo stack, al contrario di allocare , che alloca sull'heap . Questa memoria non sopravviverà alla dichiarazione di ritorno, ma è utile per un buffer di memoria virtuale.

  3. Realizzare un enorme buffer temporaneo su Windows di cui non si utilizza molto non è gratuito. Questo perché il compilatore genererà un loop sonda stack che viene chiamato ogni volta che si immette la funzione per assicurarsi che lo stack esista (perché Windows utilizza una singola pagina di guardia alla fine dello stack per rilevare quando è necessario aumentarlo. Se accedi alla memoria più di una pagina dalla fine dello stack, andrai in crash). Esempio:

void myfunction()
{
   char big[10000000];
   // Do something that only uses for first 1K of big 99% of the time.
}

Ri "al contrario di allocare": Vuoi dire "al contrario di malloc"?
Peter Mortensen,

Quanto è portatile alloca?
Peter Mortensen,

@PeterMortensen non è POSIX, la portabilità non è garantita.
Don Neufeld,

135

Altri hanno risposto direttamente alla tua domanda, ma quando provo a capire lo stack e l'heap, penso che sia utile considerare il layout di memoria di un processo UNIX tradizionale (senza thread e mmap()allocatori basati). La pagina Web del Glossario di gestione della memoria presenta un diagramma di questo layout di memoria.

Lo stack e l'heap si trovano tradizionalmente alle estremità opposte dello spazio di indirizzi virtuale del processo. Lo stack cresce automaticamente quando vi si accede, fino a una dimensione impostata dal kernel (che può essere regolata con setrlimit(RLIMIT_STACK, ...)). L'heap cresce quando l'allocatore di memoria richiama la chiamata di sistema brk()o sbrk(), mappando più pagine di memoria fisica nello spazio di indirizzi virtuale del processo.

Nei sistemi senza memoria virtuale, come alcuni sistemi integrati, si applica spesso lo stesso layout di base, tranne che per lo stack e l'heap sono di dimensioni fisse. Tuttavia, in altri sistemi embedded (come quelli basati su microcontrollori PIC Microchip), lo stack del programma è un blocco separato di memoria che non è indirizzabile dalle istruzioni di spostamento dei dati e può essere modificato o letto indirettamente solo tramite le istruzioni del flusso di programma (call, ritorno, ecc.). Altre architetture, come i processori Intel Itanium, hanno più stack . In questo senso, lo stack è un elemento dell'architettura della CPU.


117

Che cos'è uno stack?

Una pila è una pila di oggetti, in genere uno che è organizzato in modo ordinato.

Inserisci qui la descrizione dell'immagine

Le pile nelle architetture informatiche sono aree di memoria in cui i dati vengono aggiunti o rimossi in un modo last-in-first-out.
In un'applicazione multi-thread, ogni thread avrà il proprio stack.

Che cos'è un mucchio?

Un mucchio è una raccolta disordinata di cose ammucchiati a casaccio.

Inserisci qui la descrizione dell'immagine

Nelle architetture informatiche l'heap è un'area di memoria allocata dinamicamente che viene gestita automaticamente dal sistema operativo o dalla libreria del gestore della memoria.
La memoria dell'heap viene allocata, deallocata e ridimensionata regolarmente durante l'esecuzione del programma e ciò può causare un problema chiamato frammentazione.
La frammentazione si verifica quando gli oggetti di memoria sono allocati con spazi piccoli tra loro troppo piccoli per contenere oggetti di memoria aggiuntivi.
Il risultato netto è una percentuale dello spazio dell'heap che non è utilizzabile per ulteriori allocazioni di memoria.

Entrambi insieme

In un'applicazione multi-thread, ogni thread avrà il proprio stack. Ma tutti i diversi thread condivideranno l'heap.
Poiché i diversi thread condividono l'heap in un'applicazione multi-thread, ciò significa anche che deve esserci un certo coordinamento tra i thread in modo che non provino ad accedere e manipolare gli stessi frammenti di memoria nell'heap in lo stesso tempo.

Qual è più veloce: lo stack o l'heap? E perché?

Lo stack è molto più veloce dell'heap.
Ciò è dovuto al modo in cui la memoria è allocata nello stack.
Allocare memoria nello stack è semplice come spostare il puntatore dello stack verso l'alto.

Per le persone che non conoscono la programmazione, è probabilmente una buona idea usare lo stack poiché è più semplice.
Poiché lo stack è piccolo, ti consigliamo di utilizzarlo quando sai esattamente quanta memoria ti servirà per i tuoi dati, o se sai che la dimensione dei tuoi dati è molto piccola.
È meglio usare l'heap quando sai che avrai bisogno di molta memoria per i tuoi dati, o semplicemente non sei sicuro di quanta memoria ti servirà (come con un array dinamico).

Modello di memoria Java

Inserisci qui la descrizione dell'immagine

Lo stack è l'area di memoria in cui sono memorizzate le variabili locali (inclusi i parametri del metodo). Quando si tratta di variabili oggetto, questi sono semplicemente riferimenti (puntatori) agli oggetti reali sull'heap.
Ogni volta che un oggetto viene istanziato, un pezzo di memoria heap viene messo da parte per contenere i dati (stato) di quell'oggetto. Poiché gli oggetti possono contenere altri oggetti, alcuni di questi dati possono infatti contenere riferimenti a tali oggetti nidificati.


115

Lo stack è una porzione di memoria che può essere manipolata tramite diverse istruzioni del linguaggio assembly chiave, come 'pop' (rimuovi e restituisce un valore dallo stack) e 'push' (invia un valore allo stack), ma chiama anche ( chiama una subroutine - questo spinge l'indirizzo per tornare allo stack) e ritorna (ritorna da una subroutine - questo fa uscire l'indirizzo dallo stack e salta ad esso). È la regione di memoria sotto il registro del puntatore dello stack, che può essere impostata in base alle esigenze. Lo stack viene inoltre utilizzato per passare argomenti alle subroutine e anche per conservare i valori nei registri prima di chiamare le subroutine.

L'heap è una porzione di memoria che viene fornita a un'applicazione dal sistema operativo, in genere attraverso un syscall come malloc. Sui sistemi operativi moderni questa memoria è un insieme di pagine a cui ha accesso solo il processo di chiamata.

La dimensione dello stack viene determinata in fase di runtime e generalmente non aumenta dopo l'avvio del programma. In un programma C, lo stack deve essere abbastanza grande da contenere ogni variabile dichiarata all'interno di ciascuna funzione. L'heap crescerà dinamicamente in base alle necessità, ma alla fine il sistema operativo sta effettuando la chiamata (spesso aumenterà l'heap di oltre il valore richiesto da malloc, quindi almeno alcuni malloc futuri non dovranno tornare al kernel per ottenere più memoria. Questo comportamento è spesso personalizzabile)

Poiché hai allocato lo stack prima di avviare il programma, non devi mai eseguire il malloc prima di poter utilizzare lo stack, quindi è un leggero vantaggio lì. In pratica, è molto difficile prevedere cosa sarà veloce e cosa sarà lento nei moderni sistemi operativi che hanno sottosistemi di memoria virtuale, perché il modo in cui le pagine vengono implementate e dove sono archiviate è un dettaglio di implementazione.


2
Vale anche la pena menzionare qui che Intel ottimizza fortemente gli accessi allo stack, in particolare cose come prevedere dove si ritorna da una funzione.
Tom Leys,

113

Penso che molte altre persone ti abbiano dato risposte prevalentemente corrette su questo argomento.

Un dettaglio che è stato perso, tuttavia, è che il "mucchio" dovrebbe in effetti probabilmente essere chiamato "negozio gratuito". Il motivo di questa distinzione è che l'archivio gratuito originale è stato implementato con una struttura di dati nota come "heap binomiale". Per tale motivo, l'allocazione da prime implementazioni di malloc () / free () era allocazione da un heap. Tuttavia, ai giorni nostri, la maggior parte dei negozi gratuiti sono implementati con strutture di dati molto elaborate che non sono un mucchio binomiale.


8
Un altro pignolo: la maggior parte delle risposte (leggermente) implica che l'uso di uno "stack" è richiesto dalla Clingua. Questo è un malinteso comune, sebbene sia il paradigma (di gran lunga) dominante per l'implementazione C99 6.2.4 automatic storage duration objects(variabili). In effetti, la parola "stack" non appare nemmeno nello C99standard linguistico: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
johne,

[@Heath] Ho un piccolo commento sulla tua risposta. Dai un'occhiata alla risposta accettata a questa domanda . Dice che il negozio gratuito molto probabilmente è lo stesso dell'heap , anche se non lo è necessariamente.
OmarOthman,

91

Puoi fare alcune cose interessanti con lo stack. Ad esempio, hai funzioni come alloca (supponendo che tu possa superare gli copiosi avvertimenti relativi al suo uso), che è una forma di malloc che utilizza specificamente lo stack, non l'heap, per la memoria.

Detto questo, gli errori di memoria basati su stack sono tra i peggiori che abbia mai visto. Se si utilizza la memoria heap e si superano i limiti del blocco allocato, si ha una discreta possibilità di innescare un errore di segmento. (Non al 100%: il tuo blocco potrebbe essere per inciso contiguo con un altro che hai precedentemente allocato.) Ma poiché le variabili create nello stack sono sempre contigue tra loro, scrivere fuori dai limiti può cambiare il valore di un'altra variabile. Ho imparato che ogni volta che sento che il mio programma ha smesso di obbedire alle leggi della logica, è probabilmente un buffer overflow.


Quanto è portatile alloca? Ad esempio, funziona su Windows? È solo per sistemi operativi simili a Unix?
Peter Mortensen,

89

Semplicemente, lo stack è dove vengono create le variabili locali. Inoltre, ogni volta che si chiama una subroutine il contatore del programma (puntatore alla successiva istruzione della macchina) e tutti i registri importanti, e talvolta i parametri vengono inseriti nello stack. Quindi tutte le variabili locali all'interno della subroutine vengono inserite nello stack (e utilizzate da lì). Al termine della subroutine, tutte quelle cose vengono rimosse dallo stack. I dati del PC e del registro vengono recuperati e rimessi dov'erano quando sono spuntati, in modo che il tuo programma possa andare per il verso giusto.

L'heap è l'area delle allocazioni dinamiche di memoria di cui sono fatte (chiamate "nuove" o "allocate" esplicite). È una struttura dati speciale che può tenere traccia di blocchi di memoria di varie dimensioni e il loro stato di allocazione.

Nei sistemi "classici" la RAM era disposta in modo tale che il puntatore dello stack si avviava nella parte inferiore della memoria, il puntatore dell'heap si avviava nella parte superiore e crescevano l'uno verso l'altro. Se si sovrappongono, si esaurisce la RAM. Tuttavia, ciò non funziona con i moderni sistemi operativi multi-thread. Ogni thread deve avere il proprio stack e quelli possono essere creati in modo dinamico.


[@TED] Perché hai detto "a volte i parametri vengono inseriti nello stack"? Quello che so è che lo sono sempre . Potresti per favore elaborare di più?
OmarOthman,

1
@OmarOthman - Lo dico perché dipende interamente dallo scrittore del compilatore / interprete cosa succede quando viene chiamata una subroutine. Il comportamento classico di Fortran è di non usare affatto uno stack. Alcune lingue supportano cose esotiche come il pass-by-name, che è effettivamente una sostituzione testuale.
TED

83

Da WikiAnwser.

Pila

Quando una funzione o un metodo chiama un'altra funzione che a sua volta chiama un'altra funzione, ecc., L'esecuzione di tutte quelle funzioni rimane sospesa fino a quando l'ultima funzione non restituisce il suo valore.

Questa catena di chiamate di funzione sospese è lo stack, poiché gli elementi nello stack (chiamate di funzione) dipendono l'uno dall'altro.

Lo stack è importante da considerare nella gestione delle eccezioni e nelle esecuzioni dei thread.

Mucchio

L'heap è semplicemente la memoria utilizzata dai programmi per memorizzare le variabili. L'elemento dell'heap (variabili) non ha dipendenze tra loro e si può sempre accedere in modo casuale in qualsiasi momento.


"Mi piace la risposta accettata meglio dal momento che è ancora più basso livello." Questa è una brutta cosa, non una buona cosa.
Razze di leggerezza in orbita

54

Pila

  • Accesso molto veloce
  • Non è necessario disallocare esplicitamente le variabili
  • Lo spazio è gestito in modo efficiente dalla CPU, la memoria non verrà frammentata
  • Solo variabili locali
  • Limite sulla dimensione dello stack (dipendente dal sistema operativo)
  • Le variabili non possono essere ridimensionate

Mucchio

  • Le variabili sono accessibili a livello globale
  • Nessun limite alla dimensione della memoria
  • Accesso (relativamente) più lento
  • Nessun uso efficiente garantito dello spazio, la memoria può frammentarsi nel tempo man mano che i blocchi di memoria vengono allocati, quindi liberati
  • Devi gestire la memoria (sei incaricato di allocare e liberare le variabili)
  • Le variabili possono essere ridimensionate usando realloc ()

50

OK, semplicemente e in poche parole, significano ordinato e non ordinato ...!

Stack : negli oggetti stack, le cose si sovrappongono l'una all'altra, significa che saranno più veloci ed efficienti da elaborare! ...

Quindi c'è sempre un indice per indicare l'oggetto specifico, anche l'elaborazione sarà più veloce, c'è anche una relazione tra gli oggetti! ...

Heap : nessun ordine, l'elaborazione sarà più lenta e i valori saranno incasinati insieme senza un ordine o indice specifico ... ci sono casuali e non c'è relazione tra di loro ... quindi i tempi di esecuzione e utilizzo potrebbero variare ...

Creo anche l'immagine qui sotto per mostrare come possono apparire:

inserisci qui la descrizione dell'immagine


49

In breve

Uno stack viene utilizzato per l'allocazione di memoria statica e un heap per l'allocazione di memoria dinamica, entrambi memorizzati nella RAM del computer.


In dettaglio

La pila

Lo stack è una struttura di dati "LIFO" (last in, first out), gestita e ottimizzata dalla CPU abbastanza da vicino. Ogni volta che una funzione dichiara una nuova variabile, viene "inserita" nello stack. Quindi ogni volta che una funzione esce, tutte le variabili inserite nello stack da quella funzione vengono liberate (vale a dire, vengono eliminate). Una volta liberata una variabile di stack, quella regione di memoria diventa disponibile per altre variabili di stack.

Il vantaggio di utilizzare lo stack per memorizzare le variabili è che la memoria è gestita per te. Non è necessario allocare memoria manualmente o liberarla quando non è più necessaria. Inoltre, poiché la CPU organizza la memoria dello stack in modo così efficiente, leggere e scrivere sulle variabili dello stack è molto veloce.

Altro può essere trovato qui .


The Heap

L'heap è un'area della memoria del tuo computer che non è gestita automaticamente per te e non è gestita in modo così stretto dalla CPU. È una regione di memoria più fluttuante (ed è più grande). Per allocare memoria sull'heap, è necessario utilizzare malloc () o calloc (), che sono funzioni C integrate. Dopo aver allocato memoria nell'heap, sei responsabile dell'utilizzo di free () per deallocare tale memoria quando non ti serve più.

In caso contrario, il programma avrà quella che è nota come perdita di memoria. Cioè, la memoria sull'heap verrà comunque messa da parte (e non sarà disponibile per altri processi). Come vedremo nella sezione debug, esiste uno strumento chiamato Valgrind che può aiutarti a rilevare perdite di memoria.

A differenza dello stack, l'heap non ha limiti di dimensione per dimensioni variabili (a parte le ovvie limitazioni fisiche del computer). La memoria dell'heap è leggermente più lenta da leggere e da scrivere, poiché è necessario utilizzare i puntatori per accedere alla memoria dell'heap. Parleremo di puntatori a breve.

A differenza dello stack, le variabili create sull'heap sono accessibili da qualsiasi funzione, ovunque nel programma. Le variabili heap hanno essenzialmente portata globale.

Altro può essere trovato qui .


Le variabili allocate nello stack vengono archiviate direttamente nella memoria e l'accesso a questa memoria è molto veloce e la sua allocazione viene gestita al momento della compilazione del programma. Quando una funzione o un metodo chiama un'altra funzione che a sua volta chiama un'altra funzione, ecc., L'esecuzione di tutte quelle funzioni rimane sospesa fino a quando l'ultima funzione non ne restituisce il valore. Lo stack è sempre riservato in un ordine LIFO, l'ultimo blocco riservato è sempre il blocco successivo da liberare. Questo rende davvero semplice tenere traccia dello stack, liberare un blocco dallo stack non è altro che regolare un puntatore.

Le variabili allocate sull'heap hanno la memoria allocata in fase di esecuzione e l'accesso a questa memoria è un po 'più lento, ma la dimensione dell'heap è limitata solo dalla dimensione della memoria virtuale. Gli elementi dell'heap non hanno dipendenze tra loro e sono sempre accessibili in modo casuale in qualsiasi momento. È possibile allocare un blocco in qualsiasi momento e liberarlo in qualsiasi momento. Ciò rende molto più complesso tenere traccia di quali parti dell'heap sono allocate o libere in un dato momento.

Inserisci qui la descrizione dell'immagine

Puoi usare lo stack se sai esattamente quanti dati devi allocare prima del tempo di compilazione e non sono troppo grandi. È possibile utilizzare l'heap se non si conosce esattamente la quantità di dati necessari in fase di esecuzione o se è necessario allocare molti dati.

In una situazione multi-thread, ogni thread avrà il proprio stack completamente indipendente, ma condividerà l'heap. Lo stack è specifico del thread e l'heap è specifico dell'applicazione. Lo stack è importante da considerare nella gestione delle eccezioni e nelle esecuzioni dei thread.

Ogni thread ottiene uno stack, mentre in genere esiste un solo heap per l'applicazione (anche se non è raro avere più heap per diversi tipi di allocazione).

Inserisci qui la descrizione dell'immagine

In fase di esecuzione, se l'applicazione necessita di più heap, può allocare memoria dalla memoria libera e se lo stack necessita di memoria, può allocare memoria dalla memoria allocata memoria libera per l'applicazione.

Anche più dettagli sono forniti qui e qui .


Ora vieni alle risposte alla tua domanda .

In che misura sono controllati dal sistema operativo o dal runtime della lingua?

Il sistema operativo alloca lo stack per ogni thread a livello di sistema quando viene creato il thread. In genere il sistema operativo viene chiamato dal runtime della lingua per allocare l'heap per l'applicazione.

Altro può essere trovato qui .

Qual è il loro scopo?

Già dato in cima.

"Puoi usare lo stack se sai esattamente quanti dati devi allocare prima del tempo di compilazione, e non è troppo grande. Puoi usare l'heap se non sai esattamente quanti dati ti occorreranno in fase di esecuzione o se devi allocare molti dati ".

Altro può essere trovato qui .

Cosa determina la dimensione di ciascuno di essi?

La dimensione dello stack viene impostata dal sistema operativo quando viene creato un thread. La dimensione dell'heap viene impostata all'avvio dell'applicazione, ma può aumentare in base allo spazio necessario (l'allocatore richiede più memoria dal sistema operativo).

Cosa rende uno più veloce?

L'allocazione dello stack è molto più veloce poiché tutto ciò che fa è spostare il puntatore dello stack. Utilizzando i pool di memoria, è possibile ottenere prestazioni comparabili dall'allocazione dell'heap, ma ciò comporta una leggera complessità aggiuntiva e il suo mal di testa.

Inoltre, stack vs. heap non è solo una considerazione delle prestazioni; ti dice anche molto sulla durata prevista degli oggetti.

I dettagli possono essere trovati da qui .


36

Negli anni '80, UNIX si propagò come coniglietti con grandi compagnie che si lanciavano da sole. Exxon ne aveva uno così come dozzine di nomi di marchi persi nella storia. Il modo in cui la memoria era disposta era a discrezione di molti implementatori.

Un tipico programma C è stato presentato in piano con un'opportunità per aumentare modificando il valore brk (). In genere, HEAP era appena al di sotto di questo valore e l'aumento della quantità aumentava la quantità di heap disponibile.

Il singolo STACK era in genere un'area sotto HEAP che era un tratto di memoria che non conteneva nulla di valore fino all'inizio del successivo blocco fisso di memoria. Questo blocco successivo era spesso CODICE che poteva essere sovrascritto dai dati dello stack in uno dei famosi hack della sua epoca.

Un tipico blocco di memoria era BSS (un blocco di valori zero) che non è stato accidentalmente azzerato nell'offerta di un produttore. Un altro era DATA contenente valori inizializzati, inclusi stringhe e numeri. Un terzo era CODICE contenente CRT (runtime C), main, funzioni e librerie.

L'avvento della memoria virtuale in UNIX modifica molti dei vincoli. Non vi è alcuna ragione oggettiva per cui questi blocchi debbano essere contigui, di dimensioni fisse o ordinati in un modo particolare ora. Naturalmente, prima di UNIX c'era Multics che non soffriva di questi vincoli. Ecco uno schema che mostra uno dei layout di memoria di quell'epoca.

Un tipico layout di memoria del programma UNIX C in stile anni '80



26

Un paio di centesimi: penso che sarà bello disegnare una memoria grafica e più semplice:

Questa è la mia visione della costruzione della memoria di processo con semplificazione per una più facile comprensione di ciò che sta accadendo


Frecce: mostrano dove crescere stack e heap, le dimensioni dello stack del processo hanno un limite, definito nel sistema operativo, i limiti della dimensione dello stack del thread in base ai parametri nell'API di creazione del thread di solito. L'heap generalmente limita la dimensione massima della memoria virtuale del processo, ad esempio per 32 bit 2-4 GB.

Modo semplice: process heap è generale per process e tutti i thread all'interno, usando per l'allocazione della memoria nel caso comune con qualcosa come malloc () .

Lo stack è una memoria rapida per l'archiviazione in puntatori e variabili di ritorno di funzione comune, elaborati come parametri nella chiamata di funzione, variabili di funzione locali.


23

Dato che alcune risposte sono andate male, contribuirò il mio acaro.

Sorprendentemente, nessuno ha menzionato il fatto che più stack (cioè non correlati al numero di thread in esecuzione a livello di sistema operativo) si trovano non solo in linguaggi esotici (PostScript) o piattaforme (Intel Itanium), ma anche in fibre , thread verdi e alcune implementazioni di coroutine .

Fibre, fili verdi e coroutine sono per molti versi simili, il che porta a molta confusione. La differenza tra fibre e fili verdi è che il primo utilizza il multitasking cooperativo, mentre il secondo può presentare uno cooperativo o preventivo (o anche entrambi). Per la distinzione tra fibre e coroutine, vedi qui .

In ogni caso, lo scopo di entrambe le fibre, i fili verdi e le coroutine è avere più funzioni in esecuzione contemporaneamente, ma non in parallelo (vedi questa domanda SO per la distinzione) all'interno di un singolo thread a livello di sistema operativo, trasferendo il controllo avanti e indietro l'uno dall'altro in modo organizzato.

Quando si utilizzano fibre, fili verdi o coroutine, di solito si dispone di una pila separata per funzione. (Tecnicamente, non solo uno stack ma un intero contesto di esecuzione è per funzione. Soprattutto, la CPU registra.) Per ogni thread ci sono tanti stack quante sono le funzioni in esecuzione contemporaneamente, e il thread passa tra l'esecuzione di ogni funzione secondo la logica del tuo programma. Quando una funzione termina, il suo stack viene distrutto. Quindi, il numero e la durata delle pile sono dinamici e non sono determinati dal numero di thread a livello di sistema operativo!

Si noti che ho detto "di solito hanno uno stack separato per funzione". Ci sono entrambi stackful e stackless implementazioni di couroutines. La maggior parte delle implementazioni notevole stackful C ++ sono Boost.Coroutine e Microsoft PPL s' async/await. (Tuttavia, le funzioni di ripristino di C ++ (aka " asynce await"), che sono state proposte a C ++ 17, probabilmente useranno coroutine impilabili.)

La proposta di Fibre alla libreria standard C ++ è imminente. Inoltre, ci sono alcune librerie di terze parti . I fili verdi sono estremamente popolari in lingue come Python e Ruby.


19

Ho qualcosa da condividere, anche se i punti principali sono già coperti.

Pila

  • Accesso molto veloce.
  • Memorizzato nella RAM.
  • Le chiamate di funzione vengono caricate qui insieme alle variabili locali e ai parametri di funzione passati.
  • Lo spazio viene liberato automaticamente quando il programma esce da un ambito.
  • Memorizzato nella memoria sequenziale.

Mucchio

  • Accesso lento rispetto allo Stack.
  • Memorizzato nella RAM.
  • Le variabili create dinamicamente vengono archiviate qui, il che in seguito richiede di liberare la memoria allocata dopo l'uso.
  • Memorizzato ovunque venga effettuata l'allocazione della memoria, accessibile sempre dal puntatore.

Nota interessante:

  • Se le chiamate di funzione fossero state memorizzate nell'heap, ciò avrebbe comportato 2 punti disordinati:
    1. A causa dell'archiviazione sequenziale in pila, l'esecuzione è più veloce. L'archiviazione in heap avrebbe comportato un enorme consumo di tempo, rallentando così l'intero programma.
    2. Se le funzioni fossero state memorizzate nell'heap (memoria disordinata puntata dal puntatore), non ci sarebbe stato modo di tornare indietro all'indirizzo del chiamante (quale stack fornisce a causa della memoria sequenziale in memoria).

1
conciso e pulito. bello :)
ingconti il

13

Wow! Così tante risposte e non credo che uno di loro abbia capito bene ...

1) Dove e cosa sono (fisicamente nella memoria di un vero computer)?

Lo stack è la memoria che inizia come l'indirizzo di memoria più alto assegnato all'immagine del programma e quindi diminuisce di valore da lì. È riservato ai parametri di funzione chiamati e a tutte le variabili temporanee utilizzate nelle funzioni.

Ci sono due cumuli: pubblico e privato.

L'heap privato inizia su un limite di 16 byte (per programmi a 64 bit) o ​​su un limite di 8 byte (per programmi a 32 bit) dopo l'ultimo byte di codice nel programma, quindi aumenta di valore da lì. Viene anche chiamato heap predefinito.

Se l'heap privato diventa troppo grande, si sovrapporrà all'area dello stack, così come lo stack si sovrapporrà all'heap se diventa troppo grande. Poiché lo stack inizia con un indirizzo più alto e scende fino a un indirizzo più basso, con un hacking adeguato puoi ottenere uno stack così grande da sovraccaricare l'area dell'heap privata e sovrapporre l'area del codice. Il trucco è quindi sovrapporre abbastanza l'area del codice che puoi agganciare al codice. È un po 'complicato da fare e si rischia un arresto anomalo del programma, ma è facile e molto efficace.

L'heap pubblico risiede nel proprio spazio di memoria al di fuori dello spazio dell'immagine del programma. È questa memoria che verrà sifonata sul disco rigido se le risorse di memoria scarseggiano.

2) In che misura sono controllati dal sistema operativo o dal runtime della lingua?

Lo stack è controllato dal programmatore, l'heap privato è gestito dal sistema operativo e l'heap pubblico non è controllato da nessuno perché è un servizio del sistema operativo: fai richieste e vengono concesse o negate.

2b) Qual è il loro scopo?

Sono tutti globali per il programma, ma i loro contenuti possono essere privati, pubblici o globali.

2c) Cosa determina la dimensione di ciascuno di essi?

Le dimensioni dello stack e dell'heap privato sono determinate dalle opzioni di runtime del compilatore. L'heap pubblico viene inizializzato in fase di runtime utilizzando un parametro size.

2d) Cosa rende più veloce?

Non sono progettati per essere veloci, sono progettati per essere utili. Il modo in cui il programmatore li utilizza determina se sono "veloci" o "lenti"

REF:

https://norasandler.com/2019/02/18/Write-a-Compiler-10.html

https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-getprocessheap

https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-heapcreate


8

Molte risposte sono corrette come concetti, ma dobbiamo notare che l'hardware (ad esempio un microprocessore) richiede uno stack per consentire la chiamata di subroutine (CALL in linguaggio assembly ...). (OOP ragazzi lo chiameranno metodi )

Nello stack si salvano gli indirizzi di ritorno e si chiama → push / ret → pop viene gestito direttamente nell'hardware.

Puoi usare lo stack per passare i parametri .. anche se è più lento dell'uso dei registri (direbbe un guru a microprocessore o un buon libro del BIOS degli anni '80 ...)

  • Senza pila n microprocessore può funzionare. (non possiamo immaginare un programma, anche in linguaggio assembly, senza subroutine / funzioni)
  • Senza il mucchio che può. (Un programma in linguaggio assembly può funzionare senza, poiché l'heap è un concetto di sistema operativo, come malloc, ovvero una chiamata OS / Lib.

L'uso dello stack è più veloce in quanto:

  • È hardware e anche push / pop sono molto efficienti.
  • malloc richiede di accedere alla modalità kernel, usare lock / semaphore (o altre primitive di sincronizzazione) eseguendo del codice e gestire alcune strutture necessarie per tenere traccia dell'allocazione.

Che cos'è OPP? Intendi OOP ( object-oriented_programming )?
Peter Mortensen,

Vuoi dire che mallocè una chiamata del kernel?
Peter Mortensen,

1) sì, scusa .. OOP ... 2) malloc: scrivo in breve, scusa ... malloc è nello spazio utente .. ma può innescare altre chiamate .... il punto è che l'uso dell'heap può essere molto lento ...
ingconti,

" Molte risposte sono corrette come concetti, ma dobbiamo notare che l'hardware (ad esempio un microprocessore) richiede uno stack per consentire la chiamata di subroutine (CALL in linguaggio assembly ..) ". Stai confondendo lo stack della CPU (se ce n'era uno nella CPU moderna) e lo stack di runtime della lingua (uno per thread). Quando i programmatori parlano di uno stack, questo è lo stack di esecuzione del thread del runtime, ad esempio uno stack di thread NET), non stiamo parlando dello stack della CPU.
minuti

1

Lo stack è essenzialmente una memoria di facile accesso che gestisce semplicemente i suoi elementi come - bene - stack. Solo gli oggetti per i quali è nota la dimensione in anticipo possono andare in pila . Questo è il caso di numeri, stringhe, valori booleani.

L' heap è una memoria per elementi di cui non è possibile predeterminare la dimensione e la struttura esatte . Poiché gli oggetti e le matrici possono essere mutati e modificati in fase di esecuzione, devono andare nell'heap.

Fonte: Academind


0

Grazie per una discussione davvero buona, ma come un vero noob mi chiedo dove siano conservate le istruzioni? In INIZIO gli scienziati stavano decidendo tra due architetture (von NEUMANN dove tutto è considerato DATA e HARVARD dove un'area di memoria era riservata alle istruzioni e un'altra ai dati). Alla fine, siamo andati con il design di von Neumann e ora tutto è considerato "lo stesso". Questo mi ha reso difficile quando stavo imparando il montaggio https://www.cs.virginia.edu/~evans/cs216/guides/x86.html perché parlano di registri e puntatori di stack.

Tutto quanto sopra parla di DATI. La mia ipotesi è che, dal momento che un'istruzione è una cosa definita con un footprint di memoria specifico, andrebbe in pila e quindi tutti i registri di "quelli" discussi nell'assemblaggio sono in pila. Naturalmente poi è arrivata la programmazione orientata agli oggetti con istruzioni e dati che si confondevano in una struttura che era dinamica, quindi ora le istruzioni sarebbero state mantenute anche nell'heap?

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.