Domanda Cosa e dove sono lo stack e l'heap?


I linguaggi di programmazione spiegano che i tipi di valore sono creati sul pila e i tipi di riferimento sono creati sul mucchio , senza spiegare cosa sono queste due cose. Non ho letto una spiegazione chiara di questo. Capisco cosa una pila  è. 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 più veloce?

7114
2017-09-17 04:18


origine


risposte:


La pila è la memoria messa da parte come spazio per un thread di esecuzione. Quando viene chiamata una funzione, un blocco è riservato in cima allo stack per le variabili locali e alcuni dati contabili. Al termine di tale funzione, il blocco non viene utilizzato e può essere utilizzato la volta successiva che viene chiamata una funzione. Lo stack è sempre riservato in un ordine LIFO (ultimo in prima uscita); il blocco riservato più di recente è sempre il blocco successivo da liberare. Questo rende molto semplice tenere traccia dello stack; liberare un blocco dallo stack non è altro che regolare un puntatore.

L'heap è memoria riservata per l'allocazione dinamica. A differenza dello stack, non esiste un 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; esistono 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 c'è solo un heap per l'applicazione (sebbene non sia 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 lo stack viene recuperato. L'heap viene in genere assegnato all'avvio dell'applicazione dal runtime e viene recuperato quando l'applicazione (tecnicamente di processo) viene chiusa.

Cosa determina la dimensione di ciascuno di essi?  

La dimensione della pila viene impostata quando viene creato un thread. La dimensione dell'heap è impostata all'avvio dell'applicazione, ma può aumentare man mano che lo spazio è necessario (l'allocatore richiede più memoria dal sistema operativo).

Cosa rende più veloce?

Lo stack è più veloce perché il pattern di accesso rende banale allocare e deallocare 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 alla cache del processore, rendendolo molto veloce. Un altro problema di prestazioni per l'heap è che l'heap, essendo in gran parte una risorsa globale, in genere deve essere multi-threading sicuro, vale a dire che ogni allocazione e deallocazione deve essere, in genere, sincronizzata con "tutti" altri accessi al programma nel programma.

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


5227
2017-09-17 04:52



Pila:

  • Memorizzato nella RAM del computer proprio come l'heap.
  • Le variabili create nello stack andranno fuori campo e vengono automaticamente deallocate.
  • Molto più veloce da allocare rispetto alle variabili sull'heap.
  • Implementato con una struttura di dati dello stack effettiva.
  • Memorizza i dati locali, restituisce gli indirizzi, utilizzati per il passaggio dei parametri.
  • Può avere uno stack overflow quando viene usato troppo stack (principalmente da ricorsione infinita o troppo profonda, allocazioni molto grandi).
  • I dati creati nello stack possono essere utilizzati senza puntatori.
  • Dovresti utilizzare lo stack se sai esattamente quanti dati hai bisogno di allocare prima della compilazione e non è troppo grande.
  • 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 devono mai essere escluse dall'ambito. I dati vengono liberati con delete, delete[], o free.
  • Più lento da allocare rispetto alle variabili sullo stack.
  • Usato su richiesta per allocare un blocco di dati per l'utilizzo da parte del programma.
  • Può avere una frammentazione quando ci sono molte allocazioni e deallocazioni.
  • In C ++ o C, i dati creati nell'heap saranno puntati da puntatori e allocati con new o malloc rispettivamente.
  • Può avere errori di allocazione se viene richiesto di allocare un buffer troppo grande.
  • Si utilizzerà l'heap se non si conosce esattamente la quantità di dati di cui si avrà bisogno in fase di esecuzione o se è necessario allocare molti dati.
  • Responsabile per 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;

2092
2017-09-17 04:20



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 posizionano l'uno sull'altro nell'ordine in cui sono stati posizionati lì, e si può rimuovere solo quello superiore (senza rovesciare il tutto sopra).

    Stack like a stack of papers

    La semplicità di uno stack è che non è necessario mantenere una tabella contenente un record di ciascuna sezione della memoria allocata; l'unica informazione di stato che ti serve è un singolo puntatore alla fine dello stack. Per allocare e de-allocare, basta incrementare e decrementare quel singolo puntatore. Nota: a volte può essere implementato uno stack 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 raggiungere e rimuovere gli articoli in qualsiasi ordine perché non esiste un elemento "top" chiaro.

    Heap like a heap of licorice allsorts

    L'allocazione dell'heap richiede il mantenimento di una registrazione completa della memoria allocata e di ciò che non lo è, oltre a una manutenzione straordinaria per ridurre la frammentazione, trovare segmenti di memoria contigui abbastanza grandi da adattarsi alla dimensione richiesta e così via. La memoria può essere dislocata 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 intorno o la raccolta dei dati inutili, identificandosi in fase di esecuzione quando la memoria non è più nel campo di applicazione e la rilascia.

Queste immagini dovrebbero fare un buon lavoro nel descrivere i due modi di allocare e liberare memoria in uno stack e 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 a pila di chiamate  che memorizza informazioni rilevanti per la funzione corrente come un puntatore a qualsiasi funzione da cui è stato chiamato e qualsiasi variabile locale. Poiché le funzioni chiamano altre funzioni e quindi restituiscono, lo stack aumenta 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 generico utilizzato per qualsiasi memoria allocata dinamicamente e in modo casuale; cioè fuori uso. La memoria viene 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 dinamicamente, che viene solitamente gestita dal sistema operativo.

  • Qual è il loro scopo?

    Lo stack di chiamate è un concetto di basso livello che non si riferisce a "scope" nel senso della programmazione. Se si disassembla un po 'di codice, si vedranno i riferimenti relativi allo stile del puntatore a porzioni dello stack, ma per quanto riguarda un linguaggio di livello superiore, il linguaggio impone le proprie regole di ambito. Un aspetto importante di uno stack, tuttavia, è che una volta restituita una funzione, qualsiasi elemento locale a quella funzione viene immediatamente liberato dallo stack. Funziona come ti aspetteresti che funzioni, visto il funzionamento dei tuoi linguaggi di programmazione. In un heap, è anche difficile da definire. L'ambito è tutto ciò che viene esposto dal sistema operativo, ma il linguaggio di programmazione probabilmente aggiunge le sue regole su cosa sia un "ambito" nell'applicazione. L'architettura del processore e il sistema operativo utilizzano l'indirizzamento virtuale, che il processore traduce in indirizzi fisici e ci sono errori di pagina, ecc. Essi tengono traccia di quali pagine appartengono a quali applicazioni. Non devi mai preoccuparti di questo, però, perché usi solo il metodo utilizzato dal tuo linguaggio di programmazione per allocare e liberare memoria, e controlla gli errori (se l'assegnazione / liberazione fallisce per qualsiasi motivo).

  • Cosa determina la dimensione di ciascuno di essi?

    Di nuovo, dipende dalla lingua, dal compilatore, dal sistema operativo e dall'architettura. Solitamente uno stack viene pre-allocato, perché per definizione deve essere una memoria contigua (più su questo nell'ultimo paragrafo). Il compilatore di lingue o il sistema operativo determinano le sue dimensioni. Non si immagazzinano enormi quantità di dati nello stack, quindi sarà abbastanza grande da non essere mai utilizzato completamente, tranne nei casi di ricorsione infinita indesiderata (quindi, "stack overflow") o altre decisioni di programmazione insolite.

    Un heap è un termine generico per tutto ciò che può essere assegnato dinamicamente. 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 di solito non devi preoccuparti molto di come funziona in profondità, tranne che (nelle lingue dove ti consente) non devi usare la memoria non hai ancora assegnato o memoria che hai liberato.

  • Cosa rende più veloce?

    Lo stack è più veloce perché tutta la memoria libera è sempre contigua. Non è necessario mantenere alcuna lista di tutti i segmenti della memoria libera, un solo puntatore alla parte superiore della pila corrente. Generalmente i compilatori memorizzano questo puntatore in un modo speciale, veloce Registrare  per questo scopo. Inoltre, le operazioni successive su uno stack sono solitamente concentrate in aree molto vicine della memoria, che a un livello molto basso sono utili per l'ottimizzazione dalle cache del processore on-die.


1259
2018-03-19 14:38



(Ho spostato questa risposta da un'altra domanda che è stata più o meno una faccenda di questo.)

La risposta alla tua domanda è specifica dell'implementazione e può variare tra compilatori e architetture di processore. Tuttavia, ecco una spiegazione semplificata.

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

Il mucchio

  • L'heap contiene un elenco collegato di blocchi usati e liberi. Nuove allocazioni nell'heap (di new o malloc) sono soddisfatti creando un blocco adatto da uno dei blocchi liberi. Ciò richiede l'aggiornamento dell'elenco di blocchi nell'heap. Questo meta informazione  i blocchi sull'heap sono anche archiviati nell'heap spesso in una piccola area proprio di fronte a ogni blocco.
  • Man mano che l'heap cresce, i nuovi blocchi vengono spesso assegnati da indirizzi inferiori a indirizzi superiori. Quindi puoi pensare all'heap come a mucchio  di blocchi di memoria che aumentano di dimensioni man mano che la memoria viene allocata. Se l'heap è troppo piccolo per un'allocazione, la dimensione può essere aumentata spesso acquisendo più memoria dal sistema operativo sottostante.
  • L'allocazione e la deallocazione di molti piccoli blocchi può lasciare l'heap in uno stato in cui vi sono molti piccoli blocchi liberi intervallati tra i blocchi utilizzati. Una richiesta di 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 potrebbe essere abbastanza grande. Questo è chiamato frammentazione dell'heap .
  • Quando un blocco utilizzato 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 che riduce effettivamente la frammentazione dell'heap.

The heap

Lo stack

  • Lo stack lavora spesso in stretto tandem con uno speciale registro 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 spingendo  valori sullo stack e popping  li indietro dalla pila. Ogni spingere  memorizza il valore nella posizione corrente del puntatore dello stack e diminuisce il puntatore dello stack. UN pop  recupera il valore puntato dal puntatore dello stack e quindi aumenta il puntatore dello stack (non essere confuso dal fatto che aggiungendo  un valore in pila diminuisce  il puntatore dello stack e rimozione  un valore aumenta  esso. Ricorda che lo stack cresce fino in fondo). I valori memorizzati e recuperati sono i valori dei registri della CPU.
  • Quando viene chiamata una funzione, la CPU utilizza istruzioni speciali che spingono la corrente puntatore di istruzioni , cioè l'indirizzo del codice in esecuzione sullo stack. La CPU quindi salta alla funzione impostando il puntatore di istruzioni all'indirizzo della funzione chiamata. Successivamente, quando la funzione ritorna, il vecchio puntatore di istruzioni viene estratto dallo stack e l'esecuzione riprende dal 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 messi in pila prima della chiamata alla funzione. Il codice nella funzione è quindi in grado di spostarsi verso l'alto dalla pila dal puntatore dello stack corrente per individuare questi valori.
  • Le chiamate alle funzioni di nidificazione funzionano come un incantesimo. Ogni nuova chiamata allocherà i parametri di funzione, l'indirizzo di ritorno e lo spazio per le variabili locali e questi record di attivazione  può essere impilato per le chiamate annidate e si svolgerà nel modo corretto quando le funzioni ritornano.
  • Poiché lo stack è un blocco limitato di memoria, puoi causare a stack overflow  chiamando troppe funzioni annidate e / o allocando troppo spazio per le variabili locali. Spesso l'area di memoria utilizzata per lo stack viene impostata in modo tale che la scrittura al di sotto del fondo (l'indirizzo più basso) dello stack attiverà un 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.

The stack

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

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

Il modo in cui viene gestito l'heap dipende in realtà dall'ambiente di runtime. C usa malloc e C ++ utilizza 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 alla libreria che gestisce l'heap. Tuttavia, la crescita dello stack è spesso impossibile in quanto l'overflow dello stack viene scoperto solo quando è troppo tardi; e l'interruzione del thread di esecuzione è l'unica opzione praticabile.


663
2017-07-31 15:54



Nel seguente codice C #

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

Ecco come viene gestita la memoria

Picture of variables on the stack

Local Variables che ha solo bisogno di durare finché l'invocazione della funzione va in pila. L'heap viene utilizzato per variabili la cui durata non è nota in anticipo, ma ci aspettiamo che durino un po '. Nella maggior parte delle lingue è fondamentale sapere al momento della compilazione quanto sia grande una variabile se vogliamo salvarla sullo stack.

Gli oggetti (che variano di dimensioni man mano che li aggiorniamo) vanno in pila perché non sappiamo al momento della creazione quanto tempo dureranno. In molti linguaggi l'heap è garbage collection 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 ha a che fare con i puntatori.

Ulteriori informazioni possono essere trovate qui:

La differenza tra stack e memoria heap «timmurphy.org

e qui:

Creazione di oggetti nello stack e heap

Questo articolo è la fonte dell'immagine sopra: Sei importanti concetti .NET: Stack, heap, tipi di valori, tipi di riferimento, boxing e unboxing: CodeProject

ma sii consapevole che potrebbe contenere alcune inesattezze.


350
2017-11-09 12:28



The Stack Quando si chiama una funzione, gli argomenti di quella funzione più qualche altro sovraccarico vengono messi in pila. Alcune informazioni (come ad esempio dove andare al ritorno) sono anche memorizzate lì. Quando dichiari una variabile all'interno della tua funzione, quella variabile viene allocata nello stack.

La deallocazione dello stack è piuttosto semplice perché si rilascia deallocate sempre nell'ordine inverso in cui viene allocata. Le risorse di pila vengono aggiunte non appena si inseriscono le funzioni, i dati corrispondenti vengono rimossi man mano che 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'ammasso L'heap è un nome generico per cui inserisci i dati che crei al volo. Se non si conosce il numero di astronavi che il programma sta per creare, è probabile che si utilizzi l'operatore nuovo (o malloc o equivalente) per creare ciascuna astronave. Questa allocazione rimarrà per un po ', quindi è probabile che libereremo le cose in un ordine diverso da quello che le abbiamo create.

Quindi, l'heap è molto più complesso, perché finiscono per essere regioni di memoria che sono inutilizzate interlacciate con blocchi che sono - la memoria si frammenta. Trovare una memoria libera delle dimensioni necessarie è un problema difficile. Questo è il motivo per cui l'heap dovrebbe essere evitato (anche se è ancora spesso usato).

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

Questo è pratico solo se l'utilizzo della memoria è alquanto diverso dalla norma, ad es. Per i giochi in cui si carica un livello in una grande operazione e si può buttare via tutto in un'altra enorme operazione.

Posizione fisica in memoria Questo è meno rilevante di quanto pensi a causa di una tecnologia chiamata Memoria virtuale  il che fa pensare al tuo programma che hai accesso a un determinato indirizzo in cui i dati fisici sono da qualche altra parte (anche sul disco fisso!). Gli indirizzi che ottieni per lo stack sono in ordine crescente man mano che l'albero delle chiamate diventa più profondo. Gli indirizzi per l'heap non sono prevedibili (vale a dire l'impianto specifico) e francamente non importanti.


190
2017-09-17 04:27



Chiarire, questa risposta  ha informazioni errate ( Tommaso  risolto la sua risposta dopo i commenti, figo :)). Altre risposte evitano semplicemente di spiegare cosa significa allocazione statica. Quindi illustrerò le tre principali forme di allocazione e in che modo si riferiscono in genere al mucchio, allo stack e al segmento di dati sottostante. Mostrerò anche alcuni esempi sia in C / C ++ che in Python per aiutare le persone a capire.

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

Tuttavia, è generalmente meglio considerare " scopo " e " tutta la vita "piuttosto che" stack "e" heap ".

L'ambito si riferisce a quali parti del codice possono accedere a una variabile. Generalmente pensiamo a ambito locale  (è accessibile solo dalla funzione corrente) rispetto a portata globale  (è possibile accedervi ovunque) sebbene lo scope possa diventare molto più complesso.

La durata si riferisce a quando una variabile viene allocata e deallocata durante l'esecuzione del programma. Di solito pensiamo a allocazione statica  (la variabile persisterà per l'intera durata del programma, rendendola utile per memorizzare le stesse informazioni attraverso diverse chiamate di funzioni) contro allocazione automatica  (la variabile persiste solo durante una singola chiamata a una funzione, rendendola utile per memorizzare le informazioni che vengono utilizzate solo durante la funzione e possono essere eliminate una volta che hai finito) contro allocazione dinamica  (variabili la cui durata è definita in fase di esecuzione, invece del tempo di compilazione come statico o automatico).

Sebbene la maggior parte dei compilatori e degli interpreti implementino questo comportamento allo stesso modo in termini di utilizzo di stack, heap, ecc., Un compilatore può talvolta interrompere queste convenzioni se lo desidera fino a quando il comportamento è 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 esistono nello stack. Come è stato sottolineato in alcuni commenti, sei libero di implementare un compilatore che non usi nemmeno uno stack o un heap, ma piuttosto alcuni altri meccanismi di archiviazione (raramente fatto, dato che stack e heap sono grandi per questo).

Fornirò del semplice codice C annotato per illustrare tutto questo. Il modo migliore per imparare è eseguire un programma sotto un debugger e osservare il comportamento. Se preferisci leggere python, vai 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 significativo del perché è importante distinguere tra durata e ambito è che una variabile può avere un ambito locale ma una durata statica, ad esempio, "someLocalStaticVariable" nell'esempio di codice sopra riportato. 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 scope locale "e quando diciamo globale di solito intendiamo" variabile allocata staticamente a livello globale "Sfortunatamente quando si tratta di cose come" variabili allocate staticamente al 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 l'inserimento della parola chiave "statico" nella dichiarazione precedente impedisce a var2 di avere un ambito globale. Tuttavia, la var1 globale ha allocazione statica. Questo non è intuitivo! Per questo motivo, cerco di non usare mai la parola "statico" quando descrivo l'ambito e dico invece qualcosa come "file" o "file 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 vita, "statico" sempre  significa che la variabile è allocata all'avvio del programma e deallocata quando il programma termina.

Alcune persone pensano a questi concetti come specifici per C / C ++. Non sono. Ad esempio, l'esempio di Python qui sotto illustra tutti e tre i tipi di allocazione (ci sono alcune sottili differenze nelle lingue interpretate che non entrerò 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

168
2017-09-17 04:48



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

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

  2. In C è possibile ottenere il beneficio dell'assegnazione della lunghezza variabile tramite l'uso di alloca , che alloca sullo stack, al contrario di alloc, che alloca sull'heap. Questa memoria non sopravviverà alla tua dichiarazione di ritorno, ma è utile per un buffer di scratch.

  3. Creare un buffer temporaneo enorme su Windows che non usi molto non è gratuito. Questo perché il compilatore genererà un loop probe di stack che viene chiamato ogni volta che viene immessa la funzione per assicurarsi che lo stack esista (poiché Windows utilizza una singola pagina di guardia alla fine dello stack per rilevare quando è necessario aumentare lo stack. Se accedi alla memoria più di una pagina dalla fine dello stack, si bloccherà). Esempio:

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

155
2017-09-17 07:16



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 su Il Glossario della gestione della memoria  la pagina web ha uno schema di questo layout di memoria.

Lo stack e l'heap si trovano tradizionalmente alle estremità opposte dello spazio degli indirizzi virtuali del processo. Lo stack cresce automaticamente quando 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 invoca il brk() o sbrk() chiamata di sistema, mappatura di più pagine di memoria fisica nello spazio degli indirizzi virtuali del processo.

Nei sistemi senza memoria virtuale, come ad esempio alcuni sistemi embedded, spesso si applica lo stesso layout di base, tranne che 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 di memoria separato che non è indirizzabile dalle istruzioni di spostamento dei dati e può essere modificato o letto solo indirettamente tramite le istruzioni del flusso del programma (chiamata, ritorno, ecc.). Altre architetture, come i processori Intel Itanium, hanno pile multiple . In questo senso, lo stack è un elemento dell'architettura della CPU.


126
2017-09-17 04:57