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

65

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 usarlo.

Non posso parlare per altri sistemi (anche se per me è ragionevole presumere che adottino una pratica simile), ma almeno su Windows, il Kernel è fondamentalmente garantito per ripulire la maggior parte delle risorse (con l'eccezione di pochi ) utilizzato da un programma dopo la chiusura del programma. Che include memoria heap, tra le altre cose.

Capisco perché vorresti chiudere un file dopo aver finito di usarlo per renderlo disponibile all'utente o perché vorresti scollegare un socket collegato a un server per risparmiare larghezza di banda, ma sembra sciocco dover microgestire TUTTA la tua memoria usata dal tuo programma.

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

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

    
posta CaptainObvious 27.12.2015 - 08:36
fonte

8 risposte

108

If I need to use a piece of memory throughout the lifespan of my program, is it really necessary to free it right before program termination?

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

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

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

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

La durata della vita dei programmi può cambiare. Forse il tuo programma è una piccola utility da riga di comando oggi, con una durata tipica inferiore ai 10 minuti e alloca memoria in porzioni di qualche kb ogni 10 secondi, quindi non è necessario liberare alcuna memoria allocata prima che il programma finisca. Più tardi il programma viene modificato e ottiene un utilizzo prolungato come parte di un processo server con una durata di diverse settimane, quindi non liberare la memoria inutilizzata non è più un'opzione, altrimenti il programma inizia a consumare tutta la memoria disponibile nel tempo . Ciò significa che dovrai rivedere l'intero programma e aggiungere successivamente il codice deallocating. Se sei fortunato, questo è un compito facile, se così non fosse, potrebbe essere così difficile che ci sono molte probabilità di perdere un posto. E quando ti trovi in quella situazione, vorresti aver aggiunto il codice "gratuito" al tuo programma in anticipo, nel momento in cui hai aggiunto il codice "malloc".

Più in generale, scrivere allocazione e codice deallocating correlato sempre conta coppia come una "buona abitudine" tra molti programmatori: facendo questo sempre, si riduce la probabilità di dimenticare il codice di deallocazione in situazioni in cui la memoria deve essere liberato.

    
risposta data 27.12.2015 - 09:36
fonte
11

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

Tuttavia a volte quello che era un programma in esecuzione breve può trasformarsi in parte di uno molto più lungo in esecuzione. Quindi la liberazione di materiale diventa necessaria. Se questo non è stato pensato almeno in una certa misura, può comportare una sostanziale rielaborazione.

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

    
risposta data 27.12.2015 - 16:10
fonte
5

Potresti usare una lingua con garbage collection (come Scheme, Ocaml, Haskell, Common Lisp e persino Java, Scala, Clojure).

(Nella maggior parte dei linguaggi GC, c'è nessun modo per esplicitamente e manualmente memoria libera! A volte, alcuni valori potrebbe essere finalizzato , ad esempio GC e runtime system chiuderebbe un valore di handle di file quando quel valore non è raggiungibile, ma non è sicuro, inaffidabile, e dovresti invece chiudere esplicitamente i tuoi handle di file, poiché la finalizzazione non è mai garantito)

Potresti anche, per il tuo programma codificato in C (o anche C ++) usare il garbage collector conservatore di Boehm . Dovresti quindi sostituire tutto malloc con GC_malloc e non preoccuparti di free -ing qualsiasi puntatore. Ovviamente è necessario capire i pro e i consensi dell'uso 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 un'intera proprietà del programma.

Finalmente, come altri hanno sottolineato, in modo esplicito free -ing la zona di memoria C allocata nell'heap è una buona pratica. Per un programma di giocattoli che non assegna molta memoria, potresti persino decidere di non%% di memoria di memoria (poiché quando processo è terminato, le sue risorse, incluso lo spazio di indirizzi virtuali , saranno liberate dal sistema operativo) .

If I need to use a piece of memory throughout the lifespan of my program, is it really necessary to free it right before program termination?

No, non è necessario farlo. E molti programmi del mondo reale non si preoccupano di liberare memoria necessaria per l'intera vita (in particolare il compilatore GCC non libera alcuni della sua memoria). Tuttavia, quando lo fai (ad esempio non ti preoccupare di free -ing qualche particolare pezzo di dati allocati dinamicamente ), è meglio commentare questo fatto per facilitare il lavoro dei futuri programmatori sullo stesso progetto. Tendo a raccomandare che la quantità di memoria non sincronizzata rimanga limitata, e di solito relativamente piccola. al totale della memoria heap utilizzata.

Si noti che il sistema free spesso non rilascia memoria nel sistema operativo (ad esempio chiamando munmap (2) sui sistemi POSIX) ma solitamente contrassegnano una zona di memoria come riutilizzabile per il futuro free . In particolare, lo spazio degli indirizzi virtuali (ad es. Come visto attraverso malloc su Linux, vedi proc (5 ) ....) potrebbe non ridursi dopo /proc/self/maps (quindi utility come free o ps riportano la stessa quantità di memoria utilizzata per il tuo processo).

    
risposta data 27.12.2015 - 11:32
fonte
3

Non è necessario , poiché in questo modo non riuscirai a eseguire correttamente il tuo programma se non ci riesci. Tuttavia, ci sono motivi per cui potresti scegliere, se ne hai la possibilità.

Uno dei casi più potenti in cui mi imbatto (più e più volte) è che qualcuno scrive un piccolo pezzo di codice di simulazione che viene eseguito nell'eseguibile. Dicono "vorremmo che questo codice fosse integrato nella simulazione". Poi chiedo loro come intendono reinizializzarsi tra le corse di monte-carlo, e mi guardano senza capire. "Che cosa intendi re-inizializzare? Esegui semplicemente il programma con nuove impostazioni?"

A volte una pulizia pulita lo rende molto più facile da usare per il tuo software. Nel caso di molti esempi, presumi di non aver mai bisogno di ripulire qualcosa, e fai delle ipotesi su come puoi gestire i dati e la loro durata di vita attorno a tali presunzioni. Quando ti sposti 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 i linguaggi gestiti si occupano della finalizzazione alla fine dei processi, o in che modo C # si occupa dell'arresto programmatico dei domini dell'applicazione. Si legano in nodi perché ci sono supposizioni che cadono attraverso le fessure in questi casi estremi.

    
risposta data 27.12.2015 - 21:55
fonte
1

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

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

    
risposta data 27.12.2015 - 14:33
fonte
1

Perché è molto probabile che tra qualche tempo vorrà modificare il tuo programma, magari integrarlo con qualcos'altro, eseguire più 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 il tuo programma.

Fai cose mentre la tua comprensione su di loro è ancora fresca.

È un piccolo investimento che potrebbe generare grossi guadagni in futuro.

    
risposta data 28.12.2015 - 11:05
fonte
1

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

Dici che senti che "cose misteriose e terribili sarebbero accadute se non avessi liberato memoria dopo aver finito di usarlo."

In termini tecnici, l'unica conseguenza è che il tuo programma continuerà a consumare più memoria fino a quando non verrà raggiunto un limite rigido (ad es. lo spazio degli indirizzi virtuali è esaurito) o le prestazioni diventeranno inaccettabili. Se il programma sta per uscire, niente 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 richiede un lotto di abilità e disciplina per scrivere codice privo di perdite. L'approccio consigliato per sviluppare questa abilità e disciplina è quello di sempre la memoria libera una volta che non è più necessaria, anche se il programma sta per finire.

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

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

Considera il caso in cui hai fatto milioni di piccole allocazioni e sono state per lo più scambiate su disco. Quando si inizia a liberare tutto, la maggior parte della memoria deve essere reinserita nella RAM in modo da poter accedere alle informazioni di contabilità, per poi scartare immediatamente i dati. Questo può fare in modo che il programma impieghi alcuni minuti solo per uscire! Per non parlare di mettere molta pressione sul disco e sulla memoria fisica durante quel periodo. Se la memoria fisica è insufficiente per cominciare (forse il programma viene chiuso perché un altro programma sta masticando molta memoria) allora le singole pagine potrebbero dover essere scambiate e ridotte diverse volte quando è necessario liberare diversi oggetti dal stessa pagina, ma non vengono liberati consecutivamente.

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

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

    
risposta data 28.12.2015 - 06:17
fonte
0

I motivi principali per pulire manualmente sono: hai meno probabilità di scambiare una vera perdita di memoria per un blocco di memoria che verrà semplicemente ripulito all'uscita, a volte ti verranno rilevati errori nell'allattore solo quando camminerai per l'intero struttura dei dati per deallocarla, e avrai un mal di testa molto più piccolo se mai refactoring in modo che qualche oggetto debba essere deallocato prima che il programma esca.

I motivi principali per cui non è necessario eseguire la pulizia manuale sono: prestazioni, prestazioni, prestazioni e la possibilità di un bug di tipo free-two o use-after-free nel codice di pulizia non necessario, che può trasformarsi in un arresto anomalo bug o exploit di sicurezza.

Se ti interessa il rendimento, vuoi sempre il profilo per scoprire dove stai perdendo tutto il tuo tempo, invece di indovinare. Un possibile compromesso è quello di avvolgere il tuo codice opzionale di pulizia in un blocco condizionale, lasciarlo attivo durante il debug in modo da ottenere i vantaggi della scrittura e del debug del codice, e poi, se e solo se hai determinato empiricamente che è troppo sovraccarico, dire al compilatore di saltare nel tuo eseguibile finale. E un ritardo nella chiusura del programma è, quasi per definizione, quasi mai nel percorso critico.

    
risposta data 28.12.2015 - 04:16
fonte

Leggi altre domande sui tag