Perché i libri .Net parlano dell'allocazione di memoria stack vs heap?


36

Sembra che ogni libro .net parli di tipi di valore rispetto a tipi di riferimento e lo indichi a indicare (spesso in modo errato) dove è archiviato ogni tipo: l'heap o lo stack. Di solito è nei primi capitoli e presentato come un fatto molto importante. Penso che sia persino coperto dagli esami di certificazione . Perché stack vs heap conta anche per gli sviluppatori (principianti) .Net? Assegni roba e funziona, giusto?


11
Alcuni autori hanno un giudizio davvero negativo su ciò che è importante insegnare ai principianti e su cosa è il rumore irrilevante. In un libro che ho visto di recente, la prima menzione dei modificatori di accesso includeva già un interno protetto , che non ho mai usato in 6 anni di C # ...
Timwi,

1
La mia ipotesi è che chiunque abbia scritto la documentazione originale .Net per quella parte ne abbia fatto una gran parte, e che la documentazione è su cui gli autori originariamente hanno basato i loro libri, e poi è rimasta semplicemente intorno.
Greg,

Dire che i tipi di valore copiano il tutto e che i riferimenti non lo fanno, avrebbe più senso e sarebbe più facile capire perché usare i riferimenti, poiché dove quei valori sono memorizzati possono essere specifici dell'implementazione e persino irrilevanti.
Trinidad,

Intervista a cult cargo?
Den,

Risposte:


37

Mi sto convincendo che la ragione principale per cui questa informazione è considerata importante è la tradizione. In ambienti non gestiti, la distinzione tra stack e heap è importante e dobbiamo allocare ed eliminare manualmente la memoria che utilizziamo. Ora, la garbage collection si occupa della gestione, quindi ignorano quel pezzetto. Non credo che il messaggio sia davvero riuscito a capire che non dobbiamo preoccuparci nemmeno del tipo di memoria utilizzata.

Come ha sottolineato Fede, Eric Lippert ha alcune cose molto interessanti da dire al riguardo: http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx .

Alla luce di tali informazioni, è possibile modificare il mio primo paragrafo in modo da leggere sostanzialmente: "Il motivo per cui le persone includono queste informazioni e presume che sia importante è a causa di informazioni errate o incomplete combinate con la necessità di queste conoscenze in passato."

Per coloro che pensano che sia ancora importante per motivi di prestazioni: quali azioni intraprenderesti per spostare qualcosa dall'heap allo stack se misurassi le cose e scoprissi che era importante? Più probabilmente, troverai un modo completamente diverso di migliorare le prestazioni per l'area problematica.


6
Ho sentito che in alcune implementazioni di framework (compatte su Xbox in particolare) è meglio usare le strutture durante il periodo di rendering (il gioco stesso) per ridurre la raccolta dei rifiuti. Utilizzeresti comunque tipi normali altrove, ma preallocato in modo che il GC non funzioni durante il gioco. Questa è l'unica ottimizzazione per quanto riguarda stack vs heap che conosco in .NET ed è piuttosto specifica per le esigenze del framework compatto e dei programmi in tempo reale.
CodexArcanum,

5
Concordo principalmente con l'argomento della tradizione. Molti programmatori esperti ad un certo punto potrebbero aver programmato in un linguaggio di basso livello in cui questa roba contava se volevi un codice corretto ed efficiente. Tuttavia, prendiamo ad esempio C ++, un linguaggio non gestito: la specifica ufficiale in realtà non dice che le variabili automatiche debbano andare nello stack, ecc. Lo standard di C ++ tratta lo stack e l'heap come dettagli di implementazione. +1
stakx,

36

Sembra che ogni libro di .NET parli di tipi di valore rispetto a tipi di riferimento e lo indichi a indicare (spesso in modo errato) dove è archiviato ciascun tipo: l'heap o lo stack. Di solito è nei primi capitoli e presentato come un fatto molto importante.

Sono completamente d'accordo; Lo vedo sempre.

Perché i libri .NET parlano dell'allocazione di memoria stack vs heap?

Una parte del motivo è perché molte persone sono arrivate in C # (o altri linguaggi .NET) da uno sfondo C o C ++. Poiché tali lingue non applicano per te le regole sulla durata della memorizzazione, ti viene richiesto di conoscere tali regole e di implementare attentamente il tuo programma per seguirle.

Ora, conoscere quelle regole e seguirle in C non richiede di capire "l'heap" e "lo stack". Ma se capisci come funzionano le strutture di dati, spesso è più facile capire e seguire le regole.

Quando si scrive un libro per principianti è naturale che un autore spieghi i concetti nello stesso ordine in cui li hanno imparati. Questo non è necessariamente l'ordine che ha senso per l'utente. Di recente sono stato redattore tecnico per il libro per principianti C # 4 di Scott Dorman, e una delle cose che mi è piaciuta è che Scott ha scelto un ordinamento abbastanza sensato per gli argomenti, piuttosto che iniziare su quelli che sono davvero argomenti avanzati nella gestione della memoria.

Un'altra parte del motivo è che alcune pagine della documentazione MSDN enfatizzano fortemente le considerazioni sull'archiviazione. Documentazione MSDN particolarmente vecchia che è ancora in circolazione fin dai primi giorni. Gran parte di quella documentazione presenta errori impercettibili che non sono mai stati escisi e bisogna ricordare che è stata scritta in un determinato momento della storia e per un particolare pubblico.

Perché stack vs heap conta anche per gli sviluppatori .NET (principianti)?

Secondo me no. Ciò che è molto più importante da capire è cose come:

  • Qual è la differenza nella semantica della copia tra un tipo di riferimento e un tipo di valore?
  • Come si comporta un parametro "ref int x"?
  • Perché i tipi di valore dovrebbero essere immutabili?

E così via.

Assegni roba e funziona, giusto?

Questo è l'ideale.

Ora, ci sono situazioni in cui è importante. La raccolta dei rifiuti è fantastica e relativamente economica, ma non è gratuita. La copia di piccole strutture intorno è relativamente economica, ma non è gratuita. Esistono scenari realistici di prestazione in cui è necessario bilanciare il costo della pressione di raccolta con il costo della copia eccessiva. In questi casi è molto utile avere una conoscenza approfondita delle dimensioni, della posizione e della durata effettiva di tutta la memoria rilevante.

Allo stesso modo, ci sono scenari di interoperabilità realistici in cui è necessario sapere cosa c'è nello stack e cosa c'è nell'heap e cosa potrebbe muoversi il garbage collector. Ecco perché C # ha caratteristiche come "fixed", "stackalloc" e così via.

Ma quelli sono tutti scenari avanzati. Idealmente, un programmatore principiante non dovrebbe preoccuparsi di nulla di tutto ciò.


2
Grazie per la risposta Eric. I tuoi recenti post sul blog sull'argomento sono ciò che mi ha spinto a pubblicare la domanda.
Greg,

13

A tutti voi manca il punto. Il motivo per cui la distinzione stack / heap è importante è l' ambito .

struct S { ... }

void f() {
    var x = new S();
    ...
 }

Una volta che x esce dall'ambito, l'oggetto che è stato creato è categoricamente sparito . Questo perché è allocato nello stack, non nell'heap. Non c'è nulla che possa essere entrato nella parte "..." del metodo che possa cambiare questo fatto. In particolare, qualsiasi assegnazione o chiamata di metodo avrebbe potuto fare solo copie della S struct, non creare nuovi riferimenti ad essa per consentirgli di sopravvivere.

class C { ... }

void f() {
     var x = new C();
     ...
}

Storia totalmente diversa! Dato che x è ora nell'heap , il suo oggetto (ovvero l' oggetto stesso , non una copia di esso) potrebbe benissimo continuare a sopravvivere dopo che x è uscito dall'ambito. In effetti, l'unico modo in cui non continuerà a vivere è se x è l'unico riferimento ad esso. Se le assegnazioni o le chiamate di metodo nella parte "..." hanno creato altri riferimenti che sono ancora "attivi" quando x esce dall'ambito, l'oggetto continuerà a vivere.

Questo è un concetto molto importante e l'unico modo per capire veramente "cosa e perché" è conoscere la differenza tra allocazione di stack e heap.


Non sono sicuro di aver visto quell'argomento presentato in precedenza nei libri insieme alla discussione pila / mucchio, ma è buono. +1
Greg,

2
A causa del modo in cui C # genera le chiusure, il codice in ...potrebbe causare la xconversione in un campo di una classe generata dal compilatore e quindi sopravvivere all'ambito indicato. Personalmente, trovo sgradevole l'idea di un sollevamento implicito, ma i progettisti del linguaggio sembrano andare per questo (al contrario di richiedere che qualsiasi variabile che viene sollevata abbia qualcosa nella sua dichiarazione per specificarlo). Per garantire la correttezza del programma, è spesso necessario tenere conto di tutti i riferimenti che possono esistere a un oggetto. Sapendo che prima che ritorni una routine, nessuna copia di un riferimento passato rimarrà utile.
supercat,

1
Per quanto riguarda le "struct are on the stack", la dichiarazione corretta è che se una posizione di memoria è dichiarata come structType foo, la posizione di memoria foocontiene il contenuto dei suoi campi; se fooè nello stack, così sono i suoi campi. Se fooè nell'heap, lo sono anche i suoi campi. se si footrova in una rete Apple II, così sono i suoi campi. Al contrario, se foofosse un tipo di classe, conterrebbe uno nullo un riferimento a un oggetto. L'unica situazione in cui si foopotrebbe dire che un tipo di classe contiene i campi dell'oggetto sarebbe se fosse l'unico campo di una classe e contenesse un riferimento a se stesso.
supercat,

+1, adoro la tua intuizione qui e penso che sia valida ... Tuttavia, non credo sia una giustificazione sul perché i libri trattino questo argomento in modo così approfondito. Sembra che quello che hai spiegato qui potrebbe sostituire quei 3 o 4 capitoli di detto libro ed essere MODO più utile.
Frank V,

1
da quello che so, le strutture non devono, né vanno sempre in pila.
Sara

5

Per quanto riguarda il PERCHÉ trattano l'argomento, sono d'accordo con @Kirk che è un concetto importante, che devi capire. Meglio conosci i meccanismi, tanto meglio puoi fare per realizzare grandi applicazioni che funzionano senza problemi.

Ora Eric Lippert sembra concordare con te sul fatto che l'argomento non è trattato correttamente dalla maggior parte degli autori. Ti consiglio di leggere il suo blog per ottenere una grande comprensione di ciò che è nascosto.


2
Bene, il post di Eric sottolinea che tutto ciò che devi sapere sono le caratteristiche evidenti del valore e dei tipi di riferimento, e non dovresti aspettarti che l'implementazione rimanga invariata. Penso che sia abbastanza elemosinare suggerire che esiste un modo efficace per implementare nuovamente C # senza uno stack, ma il suo punto è corretto: non fa parte delle specifiche del linguaggio. Quindi l'unica ragione per usare questa spiegazione che mi viene in mente è che è un'allegoria utile per i programmatori che conoscono altre lingue, in particolare C. Fintanto che sanno che è un'allegoria, che molta letteratura non chiarisce.
Jeremy,

5

Bene, ho pensato che fosse questo il punto degli ambienti gestiti. Vorrei anche arrivare a chiamare questo un dettaglio di implementazione del runtime sottostante di cui NON dovresti fare ipotesi, perché potrebbe cambiare in qualsiasi momento.

Non so molto su .NET, ma per quanto ne so, è JITted prima dell'esecuzione. La JIT, ad esempio, potrebbe eseguire analisi di escape e cosa no e all'improvviso avresti oggetti che giacciono nello stack o semplicemente in alcuni registri. Non puoi saperlo.

Suppongo che alcuni libri lo coprano semplicemente perché gli autori attribuiscono grande importanza ad esso, o perché presumono che il loro pubblico lo faccia (ad esempio se hai scritto un "C # per programmatori C ++" probabilmente dovresti trattare l'argomento).

Tuttavia, penso che non ci sia molto altro da dire che "la memoria è gestita". Altrimenti le persone potrebbero trarre conclusioni errate.


2

Devi capire come funziona l'allocazione di memoria per usarla in modo efficiente anche se non devi gestirla in modo esplicito. Questo vale praticamente per ogni astrazione nell'informatica.


2
Nelle lingue gestite, devi conoscere la differenza tra un tipo di valore e un tipo di riferimento, ma oltre a ciò, è facile rimanere avvolto attorno all'asse pensando a come viene gestito sotto il cofano. Vedi qui per un esempio: stackoverflow.com/questions/4083981/…
Robert Harvey,

Sono d'accordo con Robert

La differenza tra heap allocato e stack allocato è esattamente ciò che spiega la differenza tra valore e tipi di riferimento.
Jeremy,


1
Jeremy, la differenza tra allocazione di heap e stack non può spiegare il diverso comportamento tra tipi di valore e tipi di riferimento, perché ci sono momenti in cui sia i tipi di valore che i tipi di riferimento si trovano nell'heap e tuttavia si comportano diversamente. La cosa più importante da capire è (ad esempio) quando è necessario utilizzare riferimenti pass-by per tipi di riferimento rispetto a tipi di valore. Dipende solo da "è un tipo di valore o un tipo di riferimento", non "è nell'heap".
Tim Goodman,

2

Ci possono essere alcuni casi limite in cui ciò può fare la differenza. Lo spazio di stack predefinito è 1meg mentre l'heap è di diversi concerti. Pertanto, se la soluzione contiene un numero elevato di oggetti, è possibile rimanere senza spazio nello stack pur avendo un sacco di spazio heap.

Tuttavia, per la maggior parte è piuttosto accademico.


Sì, ma dubito che qualcuno di questi libri si prenda cura di spiegare che i riferimenti stessi sono memorizzati nello stack - quindi non importa se hai molti tipi di riferimento o molti tipi di valore, puoi comunque avere un overflow dello stack.
Jeremy,

0

Come dici tu, C # dovrebbe astrarre via la gestione della memoria, e l'allocazione tra allocazione di stack e stack sono dettagli di implementazione che in teoria lo sviluppatore non dovrebbe conoscere.

Il problema è che alcune cose sono davvero difficili da spiegare in modo intuitivo senza fare riferimento a questi dettagli di implementazione. Cerca di spiegare il comportamento osservabile quando modifichi i tipi di valori modificabili: è quasi impossibile fare a meno della distinzione stack / heap. Oppure prova a spiegare perché anche i tipi di valore nella lingua sono in primo luogo e quando li utilizzeresti? È necessario comprendere la distinzione per dare un senso alla lingua.

Nota che i libri su Python o JavaScript non fanno molto se ne parlano. Questo perché tutto è allocato o immutabile, il che significa che le diverse semantiche della copia non entrano mai in gioco. In quelle lingue funziona l'astrazione della memoria, in C # perde.

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.