free free - Cosa succede VERAMENTE quando non si libera dopo malloc?





in hc (16)


Generalmente, ogni blocco assegnato viene liberato una volta che sono sicuro che ho finito. Oggi, il punto di ingresso del mio programma potrebbe essere main(int argc, char *argv[]) , ma domani potrebbe essere foo_entry_point(char **args, struct foo *f) e digitato come puntatore a funzione.

Quindi, se ciò accade, ora ho una perdita.

Per quanto riguarda la tua seconda domanda, se il mio programma ha preso input come a = 5, allocerei lo spazio per a, o ridichiarò lo stesso spazio su un successivo a = "pippo". Questo rimarrebbe assegnato fino a quando:

  1. L'utente ha digitato 'unset a'
  2. È stata immessa la mia funzione di pulizia, o la manutenzione di un segnale o l'utente digitato 'uscire'

Non riesco a pensare a un sistema operativo moderno che non recuperi la memoria dopo l'uscita di un processo. Poi di nuovo, gratis () è economico, perché non pulire? Come altri hanno già detto, strumenti come valgrind sono ottimi per individuare perdite di cui devi davvero preoccuparti. Anche se i blocchi che l'esempio viene etichettato come "ancora raggiungibile", è solo un rumore extra nell'output quando stai cercando di assicurarti che non ci siano perdite.

Un altro mito è " Se è in main (), non devo liberarlo ", questo non è corretto. Considera quanto segue:

char *t;

for (i=0; i < 255; i++) {
    t = strdup(foo->name);
    let_strtok_eat_away_at(t);
}

Se ciò è avvenuto prima della forking / demonizzazione (e in teoria è in esecuzione per sempre), il tuo programma ha appena fatto trapelare una dimensione indeterminata di t 255 volte.

Un buon programma ben scritto dovrebbe sempre ripulire se stesso. Liberare tutta la memoria, svuotare tutti i file, chiudere tutti i descrittori, scollegare tutti i file temporanei, ecc. Questa funzione di pulizia dovrebbe essere raggiunta al termine normale o dopo aver ricevuto vari tipi di segnali fatali, a meno che non si voglia lasciare alcuni file in giro in modo da poter rilevare un arresto anomalo e riprendere.

Davvero, sii gentile con la povera anima che deve mantenere la tua roba quando passi ad altre cose ... consegnala a loro 'valgrind clean' :)

Questo è stato qualcosa che mi ha infastidito per secoli.

A scuola ci insegnano tutti (almeno, io ero) che DEVI liberare ogni puntatore che viene assegnato. Sono un po 'curioso, però, del costo reale di non liberare memoria. In alcuni casi ovvi, come quando malloc viene chiamato all'interno di un ciclo o parte di un'esecuzione di thread, è molto importante liberare così non ci sono perdite di memoria. Ma considera i seguenti due esempi:

Innanzitutto, se ho codice è qualcosa del genere:

int main()
{
    char *a = malloc(1024);
    /* Do some arbitrary stuff with 'a' (no alloc functions) */
    return 0;
}

Qual è il vero risultato qui? Il mio pensiero è che il processo muoia e quindi lo spazio dell'heap è sparito comunque, quindi non c'è nulla di male nel perdere la chiamata a free (tuttavia, riconosco l'importanza di averlo comunque per la chiusura, la manutenibilità e le buone pratiche). Ho ragione in questo pensiero?

In secondo luogo, diciamo che ho un programma che agisce un po 'come una shell. Gli utenti possono dichiarare variabili come aaa = 123 e quelle sono memorizzate in una struttura di dati dinamica per un uso successivo. Chiaramente, sembra ovvio che tu usi una soluzione che chiamerà alcune funzioni di allocazione (hashmap, lista collegata, qualcosa del genere). Per questo tipo di programma, non ha senso liberarsi mai dopo aver chiamato malloc perché queste variabili devono essere presenti in ogni momento durante l'esecuzione del programma e non c'è un buon modo (che io possa vedere) per implementarlo con spazio allocato staticamente. È un cattivo progetto avere un mucchio di memoria che viene assegnato, ma solo liberato come parte del processo che termina? Se è così, qual è l'alternativa?




Se stai usando la memoria che hai allocato, allora non stai facendo nulla di sbagliato. Diventa un problema quando si scrivono funzioni (diverse da main) che allocano memoria senza liberarla e senza renderla disponibile per il resto del programma. Quindi il tuo programma continua a funzionare con quella memoria assegnata ad esso, ma non c'è modo di usarlo. Il tuo programma e altri programmi in esecuzione sono privati ​​di quella memoria.

Modifica: Non è preciso al 100% che altri programmi in esecuzione siano privi di quella memoria. Il sistema operativo può sempre consentirgli di usarlo a scapito dello swapping del programma nella memoria virtuale ( </handwaving> ). Il punto è, però, che se il tuo programma libera memoria che non sta usando, allora è meno probabile che uno scambio di memoria virtuale sia necessario.




Non c'è alcun pericolo reale nel non liberare le variabili, ma se si assegna un puntatore a un blocco di memoria a un diverso blocco di memoria senza liberare il primo blocco, il primo blocco non è più accessibile ma occupa ancora spazio. Questa è la cosiddetta perdita di memoria, e se lo fai con regolarità, il tuo processo inizierà a consumare sempre più memoria, togliendo risorse di sistema da altri processi.

Se il processo è di breve durata, spesso si può fare a meno di farlo poiché tutta la memoria allocata viene recuperata dal sistema operativo al termine del processo, ma vorrei consigliare di prendere l'abitudine di liberare tutta la memoria per la quale non si ha più bisogno.




Hai assolutamente ragione al riguardo. Nei piccoli programmi banali in cui una variabile deve esistere fino alla morte del programma, non vi è alcun reale vantaggio nel deallocare la memoria.

In effetti, una volta ero stato coinvolto in un progetto in cui ogni esecuzione del programma era molto complessa ma relativamente di breve durata, e la decisione era di mantenere la memoria assegnata e non destabilizzare il progetto facendo errori nella sua dislocazione.

Detto questo, nella maggior parte dei programmi questa non è davvero un'opzione, o può portare a esaurire la memoria.




Hai ragione, nessun danno è fatto ed è più veloce uscire

Ci sono diverse ragioni per questo:

  • Tutti gli ambienti desktop e server rilasciano semplicemente l'intero spazio di memoria su exit (). Non sono consapevoli delle strutture di dati interne al programma come gli heap.

  • Quasi tutte le implementazioni free() non restituiscono mai comunque memoria al sistema operativo.

  • Ancora più importante, è una perdita di tempo quando viene eseguita prima dell'uscita (). All'uscita, vengono semplicemente rilasciate le pagine di memoria e lo spazio di swap. Al contrario, una serie di chiamate gratuite () brucerà il tempo della CPU e può comportare operazioni di paging del disco, mancate cache e sfratti della cache.

Per quanto riguarda la possibilità del futuro riutilizzo del codice che giustifica la certezza delle operazioni inutili: questa è una considerazione, ma non è probabilmente la modalità Agile . YAGNI!




Quasi tutti i moderni sistemi operativi recuperano tutto lo spazio di memoria allocato dopo l'uscita di un programma. L'unica eccezione che posso pensare potrebbe essere qualcosa come Palm OS in cui la memoria statica del programma e la memoria di runtime sono praticamente la stessa cosa, quindi non liberare potrebbe far sì che il programma occupi più spazio. (Sto solo speculando qui.)

Quindi, in generale, non c'è nulla di male, tranne il costo di runtime di avere più spazio di cui hai bisogno. Certamente nell'esempio che tu dai, vuoi conservare la memoria per una variabile che potrebbe essere usata fino a quando non viene cancellata.

Tuttavia, è considerato buono per liberare memoria non appena non ne hai più bisogno e per liberare tutto ciò che hai ancora in uscita dal programma. È più un esercizio per sapere quale memoria stai usando, e pensare se ne hai ancora bisogno. Se non si tiene traccia, è possibile che si verifichino perdite di memoria.

D'altra parte, l'ammonizione simile per chiudere i file in uscita ha un risultato molto più concreto: se non lo fai, i dati che hai scritto non possono essere scaricati, o se sono un file temporaneo, potrebbero non essere cancellato quando hai finito. Inoltre, gli handle di database devono avere le loro transazioni confermate e quindi chiuse quando hai finito con loro. Allo stesso modo, se stai usando un linguaggio orientato agli oggetti come C ++ o Objective C, non liberare un oggetto quando hai finito lo farà dire che il distruttore non verrà mai chiamato, e tutte le risorse che la classe è responsabile potrebbero non essere ripulite.




Sono completamente in disaccordo con tutti coloro che dicono che OP è corretto o che non ci sono danni.

Tutti parlano di sistemi operativi moderni e / o legacy.

Ma cosa succede se sono in un ambiente in cui semplicemente non ho un sistema operativo? Dove non c'è niente?

Immagina ora che tu stia usando interrupt con stile thread e allochi memoria. Nello standard ISO / IEC C: 9899 è la durata della memoria indicata come:

7.20.3 Funzioni di gestione della memoria

1 L'ordine e la contiguità della memoria allocata da chiamate successive alle funzioni calloc, malloc e realloc non sono specificate. Il puntatore restituito se l'allocazione ha esito positivo è opportunamente allineato in modo che possa essere assegnato a un puntatore a qualsiasi tipo di oggetto e quindi utilizzato per accedere a tale oggetto o una matrice di tali oggetti nello spazio allocato (finché lo spazio non viene esplicitamente deallocato) . La durata di un oggetto assegnato si estende dall'allocazione fino alla deallocazione. [...]

Quindi non deve essere dato che l'ambiente sta facendo il lavoro di liberazione per te. Altrimenti verrebbe aggiunto all'ultima frase: "O fino a quando il programma non termina".

Quindi, in altre parole: non liberare la memoria non è solo una cattiva pratica. Produce codice non portatile e non conforme alla C. Che può almeno essere visto come 'corretto, se il seguente: [...], è supportato dall'ambiente'.

Ma nei casi in cui non hai affatto OS, nessuno sta facendo il lavoro per te (so che generalmente non assegni e rialloca la memoria su sistemi embedded, ma ci sono casi in cui potresti volerlo fare.)

Quindi, parlando in generale, C (come l'OP viene etichettato), si tratta semplicemente di produrre codice errato e non portabile.




È del tutto normale lasciare la memoria non salda quando esci; malloc () assegna la memoria dall'area di memoria chiamata "l'heap" e l'heap completo di un processo viene liberato quando il processo termina.

Detto questo, uno dei motivi per cui la gente insiste ancora sul fatto che è buono liberare tutto prima di uscire è che i debugger di memoria (ad esempio valgrind su Linux) rilevano i blocchi non documentati come perdite di memoria, e se si hanno anche perdite di memoria "reali", diventa più difficile individuarli se si ottengono anche risultati "finti" alla fine.




Sì, hai ragione, il tuo esempio non fa alcun danno (almeno non sulla maggior parte dei sistemi operativi moderni). Tutta la memoria allocata dal processo verrà ripristinata dal sistema operativo una volta che il processo è terminato.

Fonte: Allocation e GC Myths (avviso PostScript!)

Assegnazione Mito 4: i programmi non-garbage-raccolti dovrebbero sempre deallocare tutta la memoria che allocano.

La verità: i deallocations omessi nel codice di esecuzione frequente causano perdite crescenti. Raramente sono accettabili. ma i programmi che conservano la maggior parte della memoria allocata fino all'uscita del programma spesso funzionano meglio senza alcuna deallocazione interveniente. Malloc è molto più facile da implementare se non è disponibile.

Nella maggior parte dei casi, deallocare la memoria appena prima dell'uscita del programma è inutile. Il sistema operativo lo reclamerà comunque. Il tocco gratuito e la pagina negli oggetti morti; il sistema operativo no.

Conseguenza: fai attenzione ai "rilevatori di perdite" che contano le allocazioni. Alcune "perdite" sono buone!

Detto questo, dovresti davvero cercare di evitare tutte le perdite di memoria!

Seconda domanda: il tuo design è ok. Se è necessario memorizzare qualcosa fino alla chiusura dell'applicazione, è possibile farlo con allocazione dinamica della memoria. Se non si conosce la dimensione richiesta in anticipo, non è possibile utilizzare la memoria allocata staticamente.




Se stai sviluppando un'applicazione da zero, puoi fare alcune scelte istruite su quando chiamare gratuitamente. Il tuo programma di esempio va bene: assegna la memoria, forse lo fai funzionare per qualche secondo, e poi si chiude, liberando tutte le risorse richieste.

Se stai scrivendo qualcos'altro, però - un server / un'applicazione a esecuzione prolungata o una libreria che deve essere utilizzata da qualcun altro, dovresti aspettarti di chiamare gratis su tutto ciò che hai Malloc.

Ignorando il lato pragmatico per un secondo, è molto più sicuro seguire l'approccio più severo e costringersi a liberare tutto ciò che si usa malloc. Se non hai l'abitudine di guardare per perdite di memoria ogni volta che si codifica, si potrebbe facilmente scatenare alcune perdite. Quindi, in altre parole, sì - puoi andartene senza di essa; si prega di fare attenzione, però.




=== E a proposito di prove future e riutilizzo del codice ? ===

Se non si scrive il codice per liberare gli oggetti, si limiterà il codice al solo uso sicuro quando si può dipendere dal fatto che la memoria viene liberata dal processo in corso di chiusura ... vale a dire un uso singolo di una certa durata progetti o progetti "throw-away" [1] ) ... dove sai quando finirà il processo.

Se scrivi il codice che libera () s tutta la tua memoria allocata dinamicamente, allora stai provando il codice futuro e lasciandolo usare da altri in un progetto più grande.

[1] in merito a progetti "da buttare". Il codice utilizzato nei progetti "Throw-away" ha un modo di non essere buttato via. La prossima cosa che sai sono passati dieci anni e il tuo codice "throw-away" è ancora in uso).

Ho sentito una storia di un tizio che ha scritto del codice solo per divertimento per far funzionare meglio il suo hardware. Ha detto " solo un hobby, non sarà grande e professionale ". Anni dopo molte persone usano il suo codice "hobby".




Sei corretto, la memoria viene automaticamente liberata quando il processo termina. Alcune persone si sforzano di non eseguire una pulizia approfondita quando il processo viene interrotto, poiché tutto verrà ceduto al sistema operativo. Tuttavia, mentre il programma è in esecuzione, è necessario liberare memoria non utilizzata. Se non lo fai, alla fine potresti esaurire o causare un numero eccessivo di pagine se il tuo set di lavoro diventa troppo grande.




Se un programma si dimentica di liberare alcuni Megabyte prima che esca, il sistema operativo li libererà. Ma se il tuo programma viene eseguito per settimane alla volta e un ciclo all'interno del programma dimentica di liberare alcuni byte in ogni iterazione, avrai una perdita di memoria che mangerà tutta la memoria disponibile nel tuo computer, a meno che non lo riavvii regolarmente basis => anche le piccole perdite di memoria potrebbero essere brutte se il programma viene utilizzato per un'attività seriamente importante anche se inizialmente non è stato progettato per uno.




Qual è il vero risultato qui?

Il tuo programma ha fatto trapelare la memoria. A seconda del sistema operativo, potrebbe essere stato ripristinato.

La maggior parte dei moderni sistemi operativi desktop recupera la perdita di memoria alla fine del processo, rendendo tristemente comune ignorare il problema, come si può vedere da molte altre risposte qui.)

Ma ti stai affidando a una funzione di sicurezza su cui non dovresti fare affidamento e il tuo programma (o funzione) potrebbe essere eseguito su un sistema in cui questo comportamento si traduce in una perdita di memoria "dura", la prossima volta.

Si potrebbe essere in esecuzione in modalità kernel, o su sistemi operativi vintage / embedded che non impiegano la protezione della memoria come compromesso. (Gli MMU occupano spazio vuoto, la protezione della memoria costa più cicli della CPU, e non è troppo chiedere a un programmatore di ripulire se stesso).

Puoi usare e riutilizzare la memoria come più ti piace, ma assicurati di aver deallocato tutte le risorse prima di uscire.




Questo codice di solito funziona, ma considera il problema del riutilizzo del codice.

Potresti aver scritto del frammento di codice che non libera memoria allocata, viene eseguito in modo tale che la memoria venga automaticamente recuperata. Sembra tutto ok

Poi qualcun altro copia il tuo frammento nel suo progetto in modo tale da essere eseguito mille volte al secondo. Quella persona ora ha un'enorme perdita di memoria nel suo programma. Non molto buono in generale, di solito fatale per un'applicazione server.

Il riutilizzo del codice è tipico nelle imprese. Di solito la società possiede tutto il codice prodotto dai suoi dipendenti e ogni reparto può riutilizzare qualsiasi cosa l'azienda possegga. Quindi, scrivendo un tale codice "dall'aspetto innocente" si causa potenziale mal di testa ad altre persone. Questo potrebbe farti licenziare.




No, #define non è vietato. L'uso improprio di #define , tuttavia, può essere disapprovato.

Ad esempio, puoi usare

#define DEBUG

nel tuo codice in modo che in seguito, puoi designare parti del tuo codice per la compilazione condizionale usando #ifdef DEBUG , solo a scopo di debug. Non penso che nessuno sano di mente vorrebbe vietare qualcosa del genere. Le macro definite con #define sono anche ampiamente utilizzate nei programmi portatili, per abilitare / disabilitare la compilazione del codice specifico della piattaforma.

Tuttavia, se stai usando qualcosa del genere

#define PI 3.141592653589793

il tuo insegnante può giustamente sottolineare che è molto meglio dichiarare PI come costante con il tipo appropriato, ad esempio,

const double PI = 3.141592653589793;

in quanto consente al compilatore di eseguire il controllo del tipo quando viene utilizzato PI .

Analogamente (come menzionato sopra da John Bode), l'uso di macro simili a funzioni potrebbe non essere approvato, specialmente in C ++ dove è possibile utilizzare i modelli. Quindi invece di

#define SQ(X) ((X)*(X))

considerare l'utilizzo

double SQ(double X) { return X * X; }

oppure, in C ++, ancora meglio,

template <typename T>T SQ(TX) { return X * X; }

Ancora una volta, l'idea è che utilizzando le funzionalità del linguaggio anziché il preprocessore, si consente al compilatore di digitare check e anche (possibilmente) generare un codice migliore.

Una volta che hai abbastanza esperienza di codifica, saprai esattamente quando è opportuno usare #define . Fino ad allora, penso che sia una buona idea per il tuo insegnante imporre determinate regole e standard di codifica, ma preferibilmente essi stessi dovrebbero sapere, ed essere in grado di spiegare, le ragioni. Un divieto generale di #define è #define senso.





c malloc free