c++ È sempre corretto*non*usare free() sulla memoria allocata?



memory-management memory-leaks (9)

Sto studiando ingegneria informatica, e ho alcuni corsi di elettronica. Ho sentito, da due dei miei professori (di questi corsi) che è possibile evitare di usare la funzione free() (dopo malloc() , calloc() , ecc.) Perché gli spazi di memoria allocati probabilmente non verranno riutilizzati allocare altra memoria. Cioè, per esempio, se assegni 4 byte e poi li rilasci avrai 4 byte di spazio che probabilmente non verranno allocati di nuovo: avrai un buco .

Penso che sia pazzesco: non si può avere un programma non-giocattolo in cui si alloca memoria sullo heap senza rilasciarlo. Ma non ho le conoscenze per spiegare esattamente perché è così importante che per ogni malloc() ci deve essere un free() .

Quindi: ci sono mai circostanze in cui potrebbe essere appropriato usare un malloc() senza usare free() ? E se no, come posso spiegarlo ai miei professori?


Answer #1

Hai detto che erano professori di elettronica. Possono essere usati per scrivere firmware / software in tempo reale, sono stati in grado di calcolare con precisione il tempo necessario all'esecuzione di qualcosa. In quei casi sapere di avere abbastanza memoria per tutte le allocazioni e non liberare e riallocare la memoria può dare un limite più facilmente calcolato sul tempo di esecuzione.

In alcuni schemi, la protezione della memoria hardware può anche essere utilizzata per assicurarsi che la routine venga completata nella memoria allocata o che generi una trappola in casi eccezionali.


Answer #2

Conosco un caso in cui liberare esplicitamente la memoria è peggio che inutile . Cioè, quando hai bisogno di tutti i tuoi dati fino alla fine della vita del processo. In altre parole, liberarli è possibile solo prima della chiusura del programma. Dal momento che qualsiasi sistema operativo moderno si occupa di liberare memoria quando un programma muore, la chiamata free() non è necessaria in quel caso. In effetti, potrebbe rallentare la terminazione del programma, poiché potrebbe essere necessario accedere a diverse pagine in memoria.


Answer #3

Facile: basta leggere l'origine di praticamente un'implementazione half-serious di malloc()/free() . Con questo intendo l'effettivo gestore della memoria che gestisce il lavoro delle chiamate. Questo potrebbe essere nella libreria di runtime, nella macchina virtuale o nel sistema operativo. Ovviamente il codice non è ugualmente accessibile in tutti i casi.

Accertarsi che la memoria non sia frammentata, unendo fori adiacenti a fori più grandi, è molto comune. Gli allocatori più seri usano tecniche più serie per garantire questo.

Quindi, supponiamo di fare tre allocazioni e de-allocazioni e di ottenere blocchi disposti in memoria in questo ordine:

+-+-+-+
|A|B|C|
+-+-+-+

Le dimensioni delle singole allocazioni non contano. poi liberi il primo e l'ultimo, A e C:

+-+-+-+
| |B| |
+-+-+-+

quando finalmente liberi B, tu (inizialmente, almeno in teoria) finisci con:

+-+-+-+
| | | |
+-+-+-+

che può essere de-frammentato in giusto

+-+-+-+
|     |
+-+-+-+

cioè un singolo blocco libero più grande, nessun frammento lasciato.

Riferimenti, come richiesto:

  • Prova a leggere il codice per dlmalloc . Sono molto più avanzato, essendo un'implementazione di qualità di produzione completa.
  • Anche nelle applicazioni integrate sono disponibili implementazioni di de-frammentazione. Vedi ad esempio queste note sul codice heap4.c in FreeRTOS .

Answer #4

Penso che l'affermazione affermata nella domanda sia assurda se presa letteralmente dal punto di vista del programmatore, ma ha la verità (almeno in parte) dal punto di vista del sistema operativo.

malloc () finirà per chiamare o mmap () o sbrk () che preleverà una pagina dal sistema operativo.

In qualsiasi programma non banale, le possibilità che questa pagina venga mai restituita al sistema operativo durante la durata di un processo sono molto piccole, anche se si libera () la maggior parte della memoria allocata. Quindi la memoria libera () è disponibile solo per lo stesso processo la maggior parte del tempo, ma non per gli altri.


Answer #5

Le altre risposte spiegano già perfettamente che le reali implementazioni di malloc() e free() creano effettivamente dei buchi (deframmentati) in grandi blocchi liberi. Ma anche se non fosse così, sarebbe comunque una cattiva idea rinunciare free() .

Il fatto è che il tuo programma ha appena assegnato (e vuole liberare) quei 4 byte di memoria. Se verrà eseguito per un lungo periodo di tempo, è molto probabile che sarà necessario allocare nuovamente solo 4 byte di memoria. Quindi, anche se quei 4 byte non si uniranno mai in uno spazio contiguo più grande, possono ancora essere riutilizzati dal programma stesso.


Answer #6

I tuoi professori stanno sollevando un punto importante. Sfortunatamente l'uso inglese è tale che non sono assolutamente sicuro di quello che dicono. Permettetemi di rispondere alla domanda in termini di programmi non giocattolo che hanno determinate caratteristiche di utilizzo della memoria e con cui ho lavorato personalmente.

Alcuni programmi si comportano bene. Allungano la memoria in onde: molte allocazioni di piccole o medie dimensioni seguite da un sacco di liberi, in cicli ripetuti. In questi programmi gli allocatori di memoria tipici funzionano piuttosto bene. Combattono blocchi liberati e alla fine di un'onda la maggior parte della memoria libera si trova in grossi blocchi contigui. Questi programmi sono piuttosto rari.

La maggior parte dei programmi si comporta male. Assegnano e deallocano la memoria più o meno a caso, in una varietà di dimensioni da molto piccole a molto grandi, e mantengono un elevato utilizzo di blocchi allocati. In questi programmi la capacità di coalesce dei blocchi è limitata e nel tempo finiscono con la memoria altamente frammentata e relativamente non contigua. Se l'utilizzo totale della memoria supera circa 1,5 GB in uno spazio di memoria a 32 bit e ci sono allocazioni di (diciamo) 10 MB o più, alla fine una delle allocazioni di grandi dimensioni avrà esito negativo. Questi programmi sono comuni.

Altri programmi liberano poca o nessuna memoria, finché non si fermano. Assegnano progressivamente memoria durante l'esecuzione, liberando solo piccole quantità e quindi si fermano, momento in cui viene liberata tutta la memoria. Un compilatore è così. Quindi è una VM. Ad esempio, il runtime .NET CLR, a sua volta scritto in C ++, probabilmente non libera mai memoria. Perché dovrebbe?

E questa è la risposta finale. In quei casi in cui il programma è sufficientemente pesante nell'utilizzo della memoria, quindi gestire la memoria usando malloc e gratuitamente non è una risposta sufficiente al problema. A meno che non si abbia la fortuna di avere a che fare con un programma ben educato, sarà necessario progettare uno o più allocatori di memoria personalizzati che pre-allocano grandi blocchi di memoria e quindi sub-allocare secondo una strategia di propria scelta. Non è possibile utilizzare gratuitamente, tranne quando il programma si ferma.

Senza sapere esattamente quello che hanno detto i professori, per programmi veramente di scala produttiva probabilmente uscirei dalla loro parte.

MODIFICARE

Ne avrò una per rispondere ad alcune delle critiche. Ovviamente SO non è un buon posto per post di questo tipo. Giusto per essere chiari: ho circa 30 anni di esperienza nella scrittura di questo tipo di software, inclusi un paio di compilatori. Non ho riferimenti accademici, solo i miei lividi. Non posso fare a meno di sentire le critiche provenire da persone con esperienze molto più strette e più brevi.

Ripeterò il mio messaggio chiave: bilanciare malloc e free non è una soluzione sufficiente per l'allocazione di memoria su larga scala nei programmi reali. La coalescenza a blocchi è normale e acquista tempo, ma non è sufficiente. Avete bisogno di allocatori di memoria seri e intelligenti, che tendono ad afferrare la memoria in blocchi (usando malloc o altro) e raramente. Questo è probabilmente il messaggio che i professori dell'OP avevano in mente e che ha frainteso.


Answer #7

È una sciocchezza totale, per esempio ci sono molte diverse implementazioni di malloc , alcuni cercano di rendere l'heap più efficiente come quello di Doug Lea o di this .


Answer #8

I tuoi professori non sbagliano, ma lo sono anche (sono almeno fuorvianti o semplificatori). La frammentazione della memoria causa problemi di prestazioni e un uso efficiente della memoria, quindi a volte è necessario prenderla in considerazione e agire per evitarlo. Un trucco classico è, se si assegnano molte cose che sono della stessa dimensione, afferrando un pool di memoria all'avvio che è un multiplo di quella dimensione e gestendo il suo utilizzo interamente internamente, garantendo così che non si verifichi la frammentazione Livello del SO (e i buchi nel tuo mappatore di memoria interno avranno esattamente le dimensioni giuste per il prossimo oggetto di quel tipo che arriva).

Ci sono intere librerie di terze parti che non fanno altro che gestire quel tipo di cose per te, e qualche volta è la differenza tra una performance accettabile e qualcosa che va troppo lentamente. malloc() e free() richiedono una notevole quantità di tempo per l'esecuzione, che inizierai a notare se li chiami molto.

Quindi evitando di usare solo in modo semplice malloc() e free() puoi evitare sia la frammentazione che i problemi di prestazioni - ma quando arrivi subito ad esso, devi sempre assicurarti di free() tutto ciò che si malloc() meno che tu non abbia un buon motivo per fare diversamente Anche quando si utilizza un pool di memoria interno, una buona applicazione free() la memoria del pool prima che esca. Sì, il sistema operativo lo ripulirà, ma se il ciclo di vita dell'applicazione viene successivamente modificato, sarebbe facile dimenticare che il pool è ancora in agguato ...

Naturalmente, le applicazioni di lunga durata devono essere scrupolose per quanto riguarda la pulizia o il riciclaggio di tutto ciò che hanno allocato, o finiscono per esaurire la memoria.


Answer #9

I tuoi professori lavorano con POSIX, per caso? Se sono abituati a scrivere molte piccole applicazioni di shell minimaliste, questo è uno scenario in cui posso immaginare che questo approccio non sarebbe poi così male: liberare l'intero heap contemporaneamente al tempo libero del sistema operativo è più rapido che liberare un mille variabili. Se ti aspetti che la tua candidatura venga eseguita per un secondo o due, potresti facilmente andare via senza alcuna de-assegnazione.

Ovviamente è ancora una cattiva pratica (i miglioramenti delle prestazioni dovrebbero sempre essere basati sul profiling, non sulla vaga sensazione di budello), e non è qualcosa che dovresti dire agli studenti senza spiegare gli altri vincoli, ma posso immaginare un sacco di guscio piccolissimo -applicazioni da scrivere in questo modo (se non si utilizza l'assegnazione statica a titolo definitivo). Se stai lavorando su qualcosa che benefici dal non liberare le tue variabili, stai lavorando in condizioni estreme di bassa latenza (nel qual caso, come puoi permetterci anche l'allocazione dinamica e C ++?: D), o sei fare qualcosa di molto, molto sbagliato (come allocare un array intero allocando mille interi uno dopo l'altro piuttosto che un singolo blocco di memoria).





heap