Utilizzo corretto di stack e heap in C ++?


122

Ho programmato per un po ', ma è stato principalmente Java e C #. Non ho mai dovuto gestire la memoria da solo. Recentemente ho iniziato a programmare in C ++ e sono un po 'confuso su quando devo memorizzare le cose sullo stack e quando memorizzarle sull'heap.

La mia comprensione è che le variabili a cui si accede molto frequentemente dovrebbero essere memorizzate nello stack e gli oggetti, le variabili usate raramente e le grandi strutture di dati dovrebbero essere tutte archiviate nell'heap. È corretto o non sono corretto?


Risposte:


242

No, la differenza tra stack e heap non è la prestazione. È durata: qualsiasi variabile locale all'interno di una funzione (qualsiasi cosa tu non malloc () o nuova) vive nello stack. Va via quando torni dalla funzione. Se vuoi che qualcosa duri più a lungo della funzione che lo ha dichiarato, devi allocarlo nell'heap.

class Thingy;

Thingy* foo( ) 
{
  int a; // this int lives on the stack
  Thingy B; // this thingy lives on the stack and will be deleted when we return from foo
  Thingy *pointerToB = &B; // this points to an address on the stack
  Thingy *pointerToC = new Thingy(); // this makes a Thingy on the heap.
                                     // pointerToC contains its address.

  // this is safe: C lives on the heap and outlives foo().
  // Whoever you pass this to must remember to delete it!
  return pointerToC;

  // this is NOT SAFE: B lives on the stack and will be deleted when foo() returns. 
  // whoever uses this returned pointer will probably cause a crash!
  return pointerToB;
}

Per una più chiara comprensione di cosa sia lo stack, provaci dall'altra parte - piuttosto che cercare di capire cosa fa lo stack in termini di un linguaggio di alto livello, cerca "stack di chiamate" e "convenzione di chiamata" e guarda cosa la macchina fa davvero quando chiami una funzione. La memoria del computer è solo una serie di indirizzi; "heap" e "stack" sono invenzioni del compilatore.


7
Sarebbe sicuro aggiungere che le informazioni di dimensioni variabili generalmente vanno nell'heap. Le uniche eccezioni di cui sono a conoscenza sono i VLA in C99 (che ha un supporto limitato) e la funzione alloca () che viene spesso fraintesa anche dai programmatori C.
Dan Olson

10
Buona spiegazione, sebbene in uno scenario multithread con frequenti allocazioni e / o deallocazioni, l'heap è un punto di contesa, che influisce sulle prestazioni. Tuttavia, Scope è quasi sempre il fattore decisivo.
peterchen

18
Certo, e new / malloc () è di per sé un'operazione lenta ed è più probabile che lo stack sia in dcache che in una riga di heap arbitraria. Queste sono considerazioni reali, ma di solito secondarie rispetto alla questione della durata della vita.
Crashworks

1
È vero "la memoria del computer è solo una serie di indirizzi;" heap "e" stack "sono invenzioni della compilazione" ?? Ho letto in molti punti che lo stack è una regione speciale della memoria del nostro computer.
Vineeth Chitteti

2
@kai Questo è un modo per visualizzarlo, ma non è necessariamente vero fisicamente parlando. Il sistema operativo è responsabile dell'allocazione dello stack e dell'heap di un'applicazione. Anche il compilatore è responsabile, ma per farlo si basa principalmente sul sistema operativo. Lo stack è limitato e l'heap no. Ciò è dovuto al modo in cui il sistema operativo gestisce l'ordinamento di questi indirizzi di memoria in qualcosa di più strutturato in modo che più applicazioni possano essere eseguite sullo stesso sistema. Heap e stack non sono gli unici, ma in genere sono gli unici due che preoccupano la maggior parte degli sviluppatori.
tsturzl

42

Direi:

Conservalo in pila, se PUOI.

Conservalo nell'heap, se NECESSARIO.

Pertanto, preferisci lo stack all'heap. Alcuni possibili motivi per cui non è possibile memorizzare qualcosa nello stack sono:

  • È troppo grande: sui programmi multithread su un sistema operativo a 32 bit, lo stack ha una dimensione piccola e fissa (almeno al momento della creazione del thread) (in genere solo pochi mega. Questo è così che puoi creare molti thread senza esaurire l'indirizzo Per i programmi a 64 bit, o per i programmi a thread singolo (comunque Linux), questo non è un grosso problema. Sotto Linux a 32 bit, i programmi a thread singolo di solito utilizzano stack dinamici che possono continuare a crescere fino a raggiungere la cima dell'heap.
  • È necessario accedervi al di fuori dell'ambito dello stack frame originale: questo è davvero il motivo principale.

È possibile, con compilatori sensibili, allocare oggetti di dimensione non fissa sull'heap (di solito array la cui dimensione non è nota al momento della compilazione).


1
Qualunque cosa più di un paio di KB di solito è meglio mettere nell'heap. Non conosco i dettagli, ma non ricordo di aver mai lavorato con uno stack di "pochi mega".
Dan Olson

2
Questo è qualcosa che all'inizio non interesserei a un utente. Per l'utente, i vettori e gli elenchi sembrano essere allocati sullo stack anche se questo STL memorizza i contenuti sull'heap. La domanda sembrava più sulla linea di decidere quando chiamare esplicitamente new / delete.
David Rodríguez - dribeas

1
Dan: Ho messo 2 concerti (Sì, G come in GIGS) sullo stack con Linux a 32 bit. I limiti dello stack dipendono dal sistema operativo.
signor Ree

6
mrree: Lo stack del Nintendo DS è di 16 kilobyte. Alcuni limiti di stack dipendono dall'hardware.
Ant

Ant: Tutti gli stack dipendono dall'hardware, dal sistema operativo e anche dal compilatore.
Viliami

24

È più sottile di quanto suggeriscano le altre risposte. Non esiste una divisione assoluta tra i dati nello stack e i dati nell'heap in base a come lo dichiari. Per esempio:

std::vector<int> v(10);

Nel corpo di una funzione, che dichiara un vector(array dinamico) di dieci numeri interi nello stack. Ma lo spazio di archiviazione gestito da vectornon è in pila.

Ah, ma (le altre risposte suggeriscono) la durata di quella memoria è limitata dalla durata della vectorstessa, che qui è basata sullo stack, quindi non fa differenza come è implementata: possiamo solo trattarla come un oggetto basato sullo stack con semantica dei valori.

Non così. Supponiamo che la funzione fosse:

void GetSomeNumbers(std::vector<int> &result)
{
    std::vector<int> v(10);

    // fill v with numbers

    result.swap(v);
}

Quindi qualsiasi cosa con una swapfunzione (e qualsiasi tipo di valore complesso dovrebbe averne una) può servire come una sorta di riferimento riassociabile ad alcuni dati di heap, in un sistema che garantisce un unico proprietario di quei dati.

Pertanto il moderno approccio C ++ consiste nel non memorizzare mai l'indirizzo dei dati dell'heap in variabili puntatore locali nude. Tutte le allocazioni di heap devono essere nascoste all'interno delle classi.

Se lo fai, puoi pensare a tutte le variabili nel tuo programma come se fossero semplici tipi di valore e dimenticare del tutto l'heap (tranne quando si scrive una nuova classe wrapper simile a un valore per alcuni dati dell'heap, che dovrebbe essere insolito) .

Devi semplicemente conservare un po 'di conoscenza speciale per aiutarti a ottimizzare: dove possibile, invece di assegnare una variabile a un'altra in questo modo:

a = b;

scambiali in questo modo:

a.swap(b);

perché è molto più veloce e non genera eccezioni. L'unico requisito è che non è necessario bcontinuare a mantenere lo stesso valore (invece otterrà ail valore di, che verrebbe cestinato a = b).

Lo svantaggio è che questo approccio costringe a restituire valori dalle funzioni tramite parametri di output invece del valore di ritorno effettivo. Ma lo stanno risolvendo in C ++ 0x con riferimenti rvalue .

Nelle situazioni più complicate di tutte, porteresti questa idea all'estremo generale e utilizzeresti una classe puntatore intelligente come quella shared_ptrche è già in tr1. (Anche se direi che se ti sembra di averne bisogno, probabilmente sei uscito dal punto debole di applicabilità dello Standard C ++.)


6

Si memorizzerà anche un elemento nell'heap se deve essere utilizzato al di fuori dell'ambito della funzione in cui è stato creato. Un idioma utilizzato con gli oggetti stack è chiamato RAII: ciò implica l'utilizzo dell'oggetto basato sullo stack come wrapper per una risorsa, quando l'oggetto viene distrutto, la risorsa verrebbe ripulita. Gli oggetti basati sullo stack sono più facili da tenere traccia di quando potresti lanciare eccezioni: non devi preoccuparti di eliminare un oggetto basato sull'heap in un gestore di eccezioni. Questo è il motivo per cui i puntatori non elaborati non vengono normalmente utilizzati nel moderno C ++, si utilizzerà un puntatore intelligente che può essere un wrapper basato sullo stack per un puntatore grezzo a un oggetto basato sull'heap.


5

Per aggiungere alle altre risposte, può anche riguardare le prestazioni, almeno un po '. Non che dovresti preoccuparti di questo a meno che non sia rilevante per te, ma:

L'allocazione nell'heap richiede la ricerca di un rilevamento di un blocco di memoria, che non è un'operazione a tempo costante (e richiede alcuni cicli e overhead). Questo può rallentare man mano che la memoria si frammenta e / o ti stai avvicinando a utilizzare il 100% del tuo spazio degli indirizzi. D'altra parte, le allocazioni dello stack sono operazioni a tempo costante, fondamentalmente "libere".

Un'altra cosa da considerare (di nuovo, davvero importante solo se diventa un problema) è che in genere la dimensione dello stack è fissa e può essere molto inferiore alla dimensione dell'heap. Quindi, se stai allocando oggetti grandi o molti piccoli oggetti, probabilmente vorrai usare l'heap; se esaurisci lo spazio dello stack, il runtime genererà l'eccezione titolare del sito. Di solito non è un grosso problema, ma un'altra cosa da considerare.


Sia l'heap che lo stack sono memoria virtuale di paging. Il tempo di ricerca nell'heap è incredibilmente veloce rispetto a quello che serve per mappare in una nuova memoria. Sotto Linux a 32 bit, posso mettere> 2gig nel mio stack. Con i Mac, penso che lo stack sia limitato a 65Meg.
signor Ree

3

Stack è più efficiente e più facile da gestire i dati con ambito.

Ma l'heap dovrebbe essere usato per qualcosa di più grande di pochi KB (è facile in C ++, basta crearne uno boost::scoped_ptrsullo stack per contenere un puntatore alla memoria allocata).

Considera un algoritmo ricorsivo che continua a richiamare se stesso. È molto difficile limitare e / o indovinare l'utilizzo totale dello stack! Mentre sull'heap, l'allocatore ( malloc()o new) può indicare out-of-memory restituendo NULLo throwing.

Fonte : kernel Linux il cui stack non è più grande di 8 KB!


Per riferimento di altri lettori: (A) Il "dovrebbe" qui è puramente l'opinione personale dell'utente, tratta al massimo da 1 citazione e 1 scenario che è improbabile che molti utenti incontrino (ricorsione). Inoltre, (B) fornisce la libreria standard std::unique_ptr, che dovrebbe essere preferita a qualsiasi libreria esterna come Boost (anche se questo alimenta le cose allo standard nel tempo).
underscore_d


1

La scelta se allocare sull'heap o sullo stack è fatta per te, a seconda di come la tua variabile è allocata. Se si alloca qualcosa in modo dinamico, utilizzando una "nuova" chiamata, si alloca dall'heap. Se si alloca qualcosa come variabile globale o come parametro in una funzione, viene allocato nello stack.


4
Sospetto che stesse chiedendo quando mettere le cose sul mucchio, non come.
Steve Rowe

0

Secondo me ci sono due fattori decisivi

1) Scope of variable
2) Performance.

Preferirei usare lo stack nella maggior parte dei casi, ma se hai bisogno di accedere a variabili esterne all'ambito puoi usare heap.

Per migliorare le prestazioni durante l'utilizzo di heap, è anche possibile utilizzare la funzionalità per creare blocchi di heap e ciò può aiutare a ottenere prestazioni piuttosto che allocare ciascuna variabile in una posizione di memoria diversa.


0

probabilmente questo è stato risposto abbastanza bene. Vorrei indicarvi la seguente serie di articoli per avere una comprensione più profonda dei dettagli di basso livello. Alex Darby ha una serie di articoli, in cui ti guida attraverso un debugger. Ecco la parte 3 sullo Stack. http://www.altdevblogaday.com/2011/12/14/cc-low-level-curriculum-part-3-the-stack/


Il collegamento sembra essere morto, ma controllando Internet Archive Wayback Machine indica che parla solo dello stack e quindi non fa nulla per rispondere alla domanda specifica qui dello stack rispetto all'heap . -1
underscore_d
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.