Se devo usare un pezzo di memoria per tutta la durata del mio programma, è davvero necessario liberarlo subito prima della fine del programma?


67

In molti libri e tutorial, ho sentito la pratica della gestione della memoria stressata e ho sentito che alcune cose misteriose e terribili sarebbero accadute se non avessi liberato memoria dopo aver finito di usarla.

Non posso parlare per altri sistemi (anche se per me è ragionevole supporre che adottino una pratica simile), ma almeno su Windows, il kernel è sostanzialmente garantito per ripulire la maggior parte delle risorse (ad eccezione di alcuni strani) utilizzate da un programma dopo la fine del programma. Che include la memoria heap, tra le altre cose.

Capisco perché vorresti chiudere un file dopo averlo usato per renderlo disponibile all'utente o perché dovresti disconnettere un socket collegato a un server per risparmiare larghezza di banda, ma sembra sciocco devi microgestire TUTTA la tua memoria utilizzata dal tuo programma.

Ora, sono d'accordo sul fatto che questa domanda è ampia poiché il modo in cui dovresti gestire la tua memoria si basa sulla quantità di memoria di cui hai bisogno e quando ne hai bisogno, quindi restringerò l'ambito di questa domanda a questo: se devo usare un pezzo di memoria per tutta la durata del mio programma, è davvero necessario liberarla prima della conclusione del programma?

Modifica: la domanda suggerita come duplicato era specifica della famiglia di sistemi operativi Unix. La sua risposta principale ha persino specificato uno strumento specifico per Linux (ad esempio Valgrind). Questa domanda ha lo scopo di coprire la maggior parte dei sistemi operativi "normali" non incorporati e perché è o non è una buona pratica liberare memoria necessaria per tutta la durata di un programma.



19
Hai taggato questa lingua agnostica ma in molte lingue (es. Java) non è necessario liberare manualmente la memoria; succede automaticamente qualche tempo dopo che l'ultimo riferimento a un oggetto non rientra nel campo di applicazione
Richard Tingle,

3
e, naturalmente, puoi scrivere C ++ senza alcuna eliminazione e andrebbe bene per la memoria
Nikko,

5
@RichardTingle Anche se non riesco a pensare a nessun altro linguaggio al di fuori della mia testa se non C e forse C ++, il tag indipendente dalla lingua era pensato per coprire tutte le lingue che non hanno molte utilità di raccolta dei rifiuti incorporate. Oggi è certamente raro. Potresti sostenere che ora puoi implementare un tale sistema in C ++, ma alla fine hai ancora la possibilità di non cancellare un pezzo di memoria.
CaptainObvious

4
Scrivi: "il kernel è sostanzialmente garantito per ripulire tutte le risorse [...] dopo la fine del programma". Questo è generalmente falso, perché non tutto è memoria, un handle o un oggetto kernel. Ma è vero per la memoria, a cui limiti immediatamente la tua domanda.
Eric Towers,

Risposte:


108

Se devo usare un pezzo di memoria per tutta la durata del mio programma, è davvero necessario liberarlo subito prima della fine del programma?

Non è obbligatorio, ma può avere benefici (oltre ad alcuni inconvenienti).

Se il programma alloca la memoria una volta durante il suo tempo di esecuzione, e altrimenti non la rilascerebbe mai fino al termine del processo, potrebbe essere un approccio ragionevole non rilasciare la memoria manualmente e fare affidamento sul sistema operativo. Su ogni moderno SO che conosco, questo è sicuro, alla fine del processo tutta la memoria allocata viene restituita in modo affidabile al sistema.
In alcuni casi, non ripulire esplicitamente la memoria allocata può essere notevolmente più rapido rispetto alla pulizia.

Tuttavia, rilasciando esplicitamente tutta la memoria alla fine dell'esecuzione,

  • durante il debug / test, gli strumenti di rilevamento delle perdite di mem non mostreranno "falsi positivi"
  • potrebbe essere molto più semplice spostare il codice che utilizza la memoria insieme all'allocazione e alla deallocazione in un componente separato e utilizzarlo successivamente in un contesto diverso in cui il tempo di utilizzo della memoria deve essere controllato dall'utente del componente

La durata dei programmi può cambiare. Forse il tuo programma è una piccola utility da riga di comando oggi, con una durata tipica di meno di 10 minuti e alloca memoria in porzioni di alcuni kb ogni 10 secondi, quindi non è necessario liberare alcuna memoria allocata prima che il programma finisca. Successivamente il programma viene modificato e ottiene un uso prolungato come parte di un processo del server con una durata di diverse settimane - quindi non liberare più la memoria inutilizzata nel mezzo non è più un'opzione, altrimenti il ​​programma inizia a consumare tutta la memoria del server disponibile nel tempo . Ciò significa che dovrai rivedere l'intero programma e aggiungere successivamente il codice di deallocazione. Se sei fortunato, questo è un compito facile, in caso contrario, potrebbe essere così difficile che le probabilità sono alte di perdere un posto. E quando ti trovi in ​​quella situazione, vorresti aver aggiunto il "libero"

Più in generale, scrivere codice di allocazione e relativo deallocazione conta sempre a coppie come una "buona abitudine" tra molti programmatori: facendo così sempre, si riduce la probabilità di dimenticare il codice di deallocazione in situazioni in cui la memoria deve essere liberata.


12
Un buon punto sullo sviluppo di buone abitudini di programmazione.
Lawrence,

4
In generale, sono pienamente d'accordo con questo consiglio. Mantenere buone abitudini con la memoria è molto importante. Tuttavia, penso che ci siano eccezioni. Ad esempio, se ciò che hai allocato è un grafico pazzo che impiegherà diversi secondi per attraversare e liberarsi "correttamente", allora miglioreresti l'esperienza utente semplicemente terminando il programma e lasciando che il sistema operativo lo annulli.
GrandOpener,

1
È sicuro su ogni moderno sistema operativo "normale" (non incorporato con protezione della memoria) e, se non lo fosse, sarebbe un grave problema. Se un processo senza privilegi può perdere in modo permanente la memoria che il sistema operativo non recupererà, eseguirlo più volte può far esaurire la memoria del sistema. La salute a lungo termine del sistema operativo non può dipendere dalla mancanza di bug nei programmi non privilegiati. (ovviamente, questo sta ignorando cose come i segmenti di memoria condivisa Unix, che sono supportati solo dallo spazio di scambio nella migliore delle ipotesi.)
Peter Cordes,

7
@GrandOpener In questo caso potresti preferire utilizzare un qualche tipo di allocatore basato su regione per questo albero, in modo da poterlo allocare in modo normale e semplicemente riposizionare l'intera area in una volta quando è il momento, invece di attraversarla e liberarla a poco a poco. È ancora "corretto".
Thomas,

3
Inoltre, se la memoria deve davvero esistere per l'intera durata del programma, potrebbe essere un'alternativa ragionevole creare una struttura sullo stack, ad esempio in main.
Kyle Strand,

11

Liberare memoria alla fine di un programma eseguito è solo una perdita di tempo della CPU. È come riordinare una casa prima di liberarla dall'orbita.

Tuttavia, a volte quello che era un programma a esecuzione breve può diventare parte di uno a esecuzione molto più lunga. Quindi liberare le cose diventa necessario. Se questo non è stato pensato almeno in una certa misura, può comportare una sostanziale rielaborazione.

Una soluzione intelligente a questo è "talloc" che ti consente di effettuare un carico di allocazioni di memoria e poi di buttarle via con una sola chiamata.


1
Supponendo che tu sappia che il tuo sistema operativo non ha perdite.
WGroleau,

5
"spreco di tempo della CPU" Anche se molto, molto poco tempo. freeè di solito molto più veloce di malloc.
Paul Draper,

@PaulDraper Aye, perdere tempo a scambiare tutto è molto più significativo.
Deduplicatore,

5

È possibile utilizzare un linguaggio con Garbage Collection (come Scheme, Ocaml, Haskell, Common Lisp e persino Java, Scala, Clojure).

(Nella maggior parte dei linguaggi GC-ed, non c'è modo di liberare esplicitamente e manualmente la memoria! A volte, alcuni valori potrebbero essere finalizzati , ad es. Il GC e il sistema di runtime chiudono un valore di gestione dei file quando quel valore non è raggiungibile; ma non lo è certo, inaffidabile, e dovresti invece chiudere esplicitamente i tuoi handle di file, poiché la finalizzazione non è mai garantita)

Potresti anche, per il tuo programma codificato in C (o anche in C ++) utilizzare il garbage collector conservatore di Boehm . Sostituiresti quindi tutto malloccon GC_malloc e non ti preoccuperesti di freeusare alcun puntatore. Naturalmente è necessario comprendere i pro ei contro dell'utilizzo del GC di Boehm. Leggi anche il manuale GC .

La gestione della memoria è una proprietà globale di un programma. In qualche modo (e la vivacità di alcuni dati dati) è non compositivo e non modulare, dal momento che una proprietà del programma intero.

Alla fine, come altri hanno sottolineato, freeè una buona pratica creare esplicitamente la zona di memoria C allocata nell'heap. Per un programma giocattolo che non alloca molta memoria, potresti persino decidere di non freememoria affatto (da quando il processo è terminato, le sue risorse, incluso il suo spazio di indirizzi virtuale , sarebbero liberate dal sistema operativo).

Se devo usare un pezzo di memoria per tutta la durata del mio programma, è davvero necessario liberarlo subito prima della fine del programma?

No, non è necessario farlo. E molti programmi del mondo reale non si preoccupano di liberare un po 'di memoria necessaria per l'intera durata della vita (in particolare il compilatore GCC non sta liberando parte della sua memoria). Tuttavia, quando lo fai (ad es. Non ti preoccupi di freeun dato particolare di dati allocati dinamicamente in C ), farai meglio a commentare questo fatto per facilitare il lavoro dei futuri programmatori sullo stesso progetto. Tendo a raccomandare che la quantità di memoria non ordinata rimanga limitata e di solito sia relativamente piccola rispetto alla memoria heap utilizzata totale.

Si noti che il sistema freespesso non rilascia memoria per il sistema operativo (ad esempio chiamando munmap (2) su sistemi POSIX) ma di solito contrassegna una zona di memoria come riutilizzabile in futuro malloc. In particolare, lo spazio degli indirizzi virtuali (ad es. Visto /proc/self/mapssu Linux, vedere proc (5) ....) potrebbe non ridursi dopo free(quindi utilità come pso topstanno riportando la stessa quantità di memoria utilizzata per il processo).


3
"A volte, alcuni valori potrebbero essere finalizzati, ad esempio il GC e il sistema di runtime chiudono un valore di gestione dei file quando quel valore è irraggiungibile" Potresti voler sottolineare che nella maggior parte dei linguaggi GCed non esiste alcuna garanzia che una memoria verrà mai recuperata, né qualsiasi finalizzatore mai eseguito, anche all'uscita: stackoverflow.com/questions/7880569/…
Deduplicator,

Personalmente preferisco questa risposta, poiché ci incoraggia a utilizzare GC (ovvero un approccio più automatizzato).
Ta Thanh Dinh,

3
@tathanhdinh Più automatizzato, anche se esclusivamente per la memoria. Completamente manuale, per tutte le altre risorse. GC funziona scambiando determinismo e memoria per una comoda gestione della memoria. E no, i finalizzatori non aiutano molto e hanno i loro problemi.
Deduplicatore,

3

Non è necessario , poiché in caso contrario non si eseguirà correttamente il programma. Tuttavia, ci sono ragioni che potresti scegliere di fare, se ti viene data la possibilità.

Uno dei casi più potenti in cui mi imbatto (ancora e ancora) è che qualcuno scrive un piccolo pezzo di codice di simulazione che viene eseguito nel suo eseguibile. Dicono "vorremmo che questo codice fosse integrato nella simulazione". Quindi chiedo loro come intendono reinizializzare tra le corse di Monte-Carlo e mi guardano in bianco. "Cosa intendi per reinizializzare? Hai appena avviato il programma con nuove impostazioni?"

A volte una pulizia pulita semplifica notevolmente l'utilizzo del software. Nel caso di molti esempi, presumi di non aver mai bisogno di ripulire qualcosa e fai delle ipotesi su come gestire i dati e la loro durata attorno a tali presunzioni. Quando ci si sposta in un nuovo ambiente in cui tali presunzioni non sono valide, interi algoritmi potrebbero non funzionare più.

Per un esempio di come possono accadere cose strane, guarda come le lingue gestite gestiscono la finalizzazione alla fine dei processi o come C # gestisce l'arresto programmatico dei domini applicativi. Si legano ai nodi perché ci sono ipotesi che cadono attraverso le fessure in questi casi estremi.


1

Ignorando le lingue in cui non si libera comunque la memoria manualmente ...

Quello che pensi come "un programma" in questo momento potrebbe diventare ad un certo punto solo una funzione o un metodo che fa parte di un programma più ampio. E poi quella funzione potrebbe essere chiamata più volte. E poi la memoria che dovresti avere "liberato manualmente" sarà una perdita di memoria. Questa è ovviamente una sentenza.


2
questo sembra semplicemente ripetere il punto sollevato (e molto meglio spiegato) nella risposta in alto che era stata pubblicata poche ore prima: "potrebbe essere molto più semplice spostare il codice che utilizza la memoria insieme all'allocazione e alla deallocazione in un componente separato e utilizzarlo in seguito in un contesto diverso in cui il tempo di utilizzo della memoria deve essere controllato dall'utente del componente ... "
moscerino del

1

Perché è molto probabile che tra qualche tempo vorrai modificare il tuo programma, magari integrarlo con qualcos'altro, eseguire diverse istanze in sequenza o in parallelo. Quindi sarà necessario liberare manualmente questa memoria, ma non ricorderai più le circostanze e ti costerà molto più tempo per capire nuovamente il tuo programma.

Fai le cose mentre la tua comprensione è ancora fresca.

È un piccolo investimento che potrebbe produrre grandi rendimenti in futuro.


2
ciò non sembra aggiungere nulla di sostanziale rispetto ai punti formulati e spiegati nelle precedenti 11 risposte. In particolare, il punto sulla possibile modifica del programma in futuro è già stato fatto tre o quattro volte
moscerino

1
@gnat In un modo contorto e oscurato - sì. In una chiara dichiarazione: no. Concentriamoci sulla qualità della risposta, non sulla rapidità.
Agent_L

1
qualità saggia, la risposta migliore sembra essere molto meglio nello spiegare questo punto
moscerino

1

No, non è necessario, ma è una buona idea.

Dici di sentire che "accadrebbero cose misteriose e terribili se non avessi liberato memoria dopo aver finito di usarlo".

In termini tecnici, l'unica conseguenza di ciò è che il programma continuerà a consumare più memoria fino a quando non viene raggiunto un limite rigido (ad esempio lo spazio degli indirizzi virtuali è esaurito) o le prestazioni diventano inaccettabili. Se il programma sta per terminare, nulla di tutto ciò è importante perché il processo cessa di esistere. Le "cose ​​misteriose e terribili" riguardano esclusivamente lo stato mentale dello sviluppatore. Trovare la fonte di una perdita di memoria può essere un incubo assoluto (questo è un eufemismo) e ci vuole molta abilità e disciplina per scrivere codice privo di perdite. L'approccio raccomandato per sviluppare questa abilità e disciplina è liberare sempre la memoria quando non è più necessaria, anche se il programma sta per terminare.

Naturalmente questo ha l'ulteriore vantaggio che il tuo codice può essere riutilizzato e adattato più facilmente, come altri hanno già detto.

Tuttavia , esiste almeno un caso in cui è meglio non liberare memoria appena prima della chiusura del programma.

Considera il caso in cui hai effettuato milioni di piccole allocazioni e queste sono state per lo più scambiate su disco. Quando si inizia a liberare tutto, la maggior parte della memoria deve essere ripristinata nella RAM in modo che sia possibile accedere alle informazioni di contabilità, solo per scartare immediatamente i dati. Questo può richiedere al programma alcuni minuti solo per uscire! Per non parlare del fatto che ho esercitato molta pressione sul disco e sulla memoria fisica durante quel periodo. Se la memoria fisica è in esaurimento per cominciare (forse il programma si sta chiudendo perché un altro programma sta consumando molta memoria), potrebbe essere necessario scambiare le pagine singole più volte quando è necessario liberare più oggetti dal stessa pagina, ma non vengono liberati consecutivamente.

Se invece il programma si interrompe, il sistema operativo scarterà semplicemente tutta la memoria che è stata scambiata su disco, il che è quasi istantaneo perché non richiede alcun accesso al disco.

È importante notare che in un linguaggio OO, chiamare il distruttore di un oggetto forzerà anche lo scambio della memoria; se devi farlo potresti liberare anche la memoria.


1
Puoi citare qualcosa per supportare l'idea che la memoria paginata deve essere cercata per deallocare? Questo è ovviamente inefficiente, sicuramente i sistemi operativi moderni sono più intelligenti di così!

1
@Jon of All Trades, i moderni sistemi operativi sono intelligenti, ma ironicamente le lingue più moderne non lo sono. Quando liberi (qualcosa), il compilatore metterà "qualcosa" nello stack, quindi chiamerà free (). Metterlo nello stack richiede di scambiarlo di nuovo nella RAM se viene sostituito.
Jeffiekins,

@JonofAllTrades una lista libera avrebbe quell'effetto, tuttavia è un'implementazione malloc piuttosto obsoleta. Ma il sistema operativo non può impedirti di utilizzare tale implementazione.

0

I motivi principali per la pulizia manuale sono: è meno probabile che si verifichi una vera perdita di memoria per un blocco di memoria che verrà semplicemente ripulito all'uscita, a volte si rilevano bug nell'allocatore solo quando si percorre l'intera struttura di dati per rilasciare esso, e avrete un mal di testa molto più piccolo, se mai refactoring in modo che un oggetto non ha bisogno di essere deallocato prima che il programma viene chiuso.

I motivi principali per non ripulire manualmente sono: prestazioni, prestazioni, prestazioni e la possibilità di un qualche tipo di bug free-double o use-after-free nel codice di pulizia non necessario, che può trasformarsi in un bug o sicurezza sfruttare.

Se ti preoccupi delle prestazioni, vuoi sempre creare un profilo per scoprire dove stai sprecando tutto il tuo tempo, invece di indovinare. Un possibile compromesso è racchiudere il codice di pulizia opzionale in un blocco condizionale, lasciarlo durante il debug in modo da ottenere i vantaggi della scrittura e del debug del codice e quindi, se e solo se hai determinato empiricamente che è troppo sovraccarico, dì al compilatore di saltarlo nel tuo eseguibile finale. E un ritardo nella chiusura del programma è, quasi per definizione, quasi mai nel percorso critico.

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.