Cosa succede VERAMENTE quando non si libera dopo malloc?


Answers

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.

Question

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?




Sei corretto, la memoria viene automaticamente liberata quando il processo termina. Alcune persone si sforzano di non effettuare una pulizia completa quando il processo viene interrotto, poiché tutto verrà abbandonato 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.




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!




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




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.




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.




C'è in realtà una sezione del OSTEP online OSTEP per un corso di laurea in sistemi operativi che discute esattamente la tua domanda.

La sezione pertinente è "Dimenticare la memoria libera" nel capitolo Memoria API a pagina 6 che fornisce la seguente spiegazione:

In alcuni casi, potrebbe sembrare che non chiamare free () sia ragionevole. Ad esempio, il tuo programma è di breve durata e presto uscirà; in questo caso, quando il processo muore, il sistema operativo pulirà tutte le pagine allocate e quindi non si verificherà alcuna perdita di memoria di per sé. Mentre questo certamente "funziona" (vedi il lato a pagina 7), è probabilmente una cattiva abitudine da sviluppare, quindi fai attenzione a scegliere una strategia del genere

Questo estratto è nel contesto dell'introduzione del concetto di memoria virtuale. Fondamentalmente, a questo punto del libro, gli autori spiegano che uno degli obiettivi di un sistema operativo è quello di "virtualizzare la memoria", cioè di far credere a ogni programma di avere accesso a uno spazio di indirizzi di memoria molto grande.

Dietro le quinte, il sistema operativo tradurrà "indirizzi virtuali" che l'utente vedrà in indirizzi reali che puntano alla memoria fisica.

Tuttavia, la condivisione di risorse come la memoria fisica richiede che il sistema operativo tenga traccia di quali processi lo stanno utilizzando. Quindi, se un processo termina, allora rientra nelle capacità e negli obiettivi di progettazione del sistema operativo recuperare la memoria del processo in modo che possa ridistribuire e condividere la memoria con altri processi.

EDIT: Il lato menzionato nell'estratto è copiato di seguito.

A PARTIRE: PERCHÉ NESSUNA MEMORIA VIENE SCIACCIATA UNA VOLTA CHE IL TUO PROCESSO È IN USCITA

Quando scrivi un programma di breve durata, potresti allocare dello spazio usando malloc() . Il programma viene eseguito e sta per essere completato: è necessario chiamare free() un paio di volte prima di uscire? Anche se sembra sbagliato, nessun ricordo verrà "perso" in alcun senso. La ragione è semplice: ci sono davvero due livelli di gestione della memoria nel sistema. Il primo livello di gestione della memoria viene eseguito dal sistema operativo, che distribuisce la memoria ai processi quando vengono eseguiti e lo riprende quando i processi escono (o altrimenti muoiono). Il secondo livello di gestione è all'interno di ciascun processo, ad esempio all'interno dell'heap quando si chiama malloc() e free() . Anche se non si riesce a chiamare free() (e quindi perdita di memoria nell'heap), il sistema operativo recupererà tutta la memoria del processo (incluse quelle pagine per codice, stack e, come rilevante qui, heap) quando il programma è finito in esecuzione. Indipendentemente dallo stato del tuo heap nello spazio degli indirizzi, il sistema operativo riprende tutte quelle pagine quando il processo muore, assicurando così che nessuna memoria venga persa nonostante il fatto che non sia stata liberata.

Pertanto, per i programmi di breve durata, la perdita di memoria spesso non causa problemi operativi (sebbene possa essere considerata una forma scadente). Quando si scrive un server di lunga durata (come un server Web o un sistema di gestione del database, che non esce mai), la perdita di memoria è un problema molto più grande e alla fine causerà un arresto anomalo quando l'applicazione esaurirà la memoria. E, naturalmente, la perdita di memoria è un problema ancora più grande all'interno di un particolare programma: il sistema operativo stesso. Mostrandoci ancora una volta: chi scrive il codice del kernel ha il compito più difficile di tutti ...

da pagina 7 del capitolo Memoria API di

OSTEP
Libri Remzi H. Arpaci-Dusseau e Andrea C. Arpaci-Dusseau Arpaci-Dusseau marzo 2015 (versione 0.90)




Penso che i tuoi due esempi siano in realtà solo uno: il free()dovrebbe accadere solo alla fine del processo, che come fai notare è inutile da quando il processo sta terminando.

Nel secondo esempio, l'unica differenza è che si consente un numero indefinito di malloc(), che potrebbe portare a esaurire la memoria. L'unico modo per gestire la situazione è controllare il codice di ritorno malloc()e agire di conseguenza.




Related



Tags

c   malloc   free