Come funzionano malloc () e free ()?


276

Voglio sapere come malloce freelavoro.

int main() {
    unsigned char *p = (unsigned char*)malloc(4*sizeof(unsigned char));
    memset(p,0,4);
    strcpy((char*)p,"abcdabcd"); // **deliberately storing 8bytes**
    cout << p;
    free(p); // Obvious Crash, but I need how it works and why crash.
    cout << p;
    return 0;
}

Le sarei davvero grato se la risposta fosse approfondita a livello di memoria, se possibile.


5
In realtà non dovrebbe dipendere dal compilatore e dalla libreria di runtime utilizzata?
Vilx-

9
dipenderà dall'implementazione CRT. Quindi non puoi generalizzarlo.
Naveen,

58
che strcpy scrive 9 byte, non 8. Non dimenticare il terminatore NULL ;-).
Evan Teran,


2
@ LưuVĩnhPhúc che è C ++. Si noti lacout <<
Braden migliori

Risposte:


385

OK alcune risposte su malloc erano già state pubblicate.

La parte più interessante è come funziona il libero (e in questa direzione, anche il malloc può essere compreso meglio).

In molte implementazioni malloc / free, free normalmente non restituisce la memoria al sistema operativo (o almeno solo in rari casi). Il motivo è che otterrai spazi vuoti nel tuo heap e quindi può succedere, che finisci i tuoi 2 o 4 GB di memoria virtuale con spazi vuoti. Questo dovrebbe essere evitato, poiché non appena la memoria virtuale sarà terminata, avrai davvero grossi problemi. L'altro motivo è che il sistema operativo può gestire solo blocchi di memoria di dimensioni e allineamento specifici. Per essere precisi: normalmente il sistema operativo può gestire solo blocchi che il gestore della memoria virtuale è in grado di gestire (il più delle volte multipli di 512 byte, ad esempio 4KB).

Quindi la restituzione di 40 byte al sistema operativo non funzionerà. Quindi cosa fa gratis?

Free inserirà il blocco di memoria nel proprio elenco di blocchi gratuiti. Normalmente tenta anche di fondere insieme blocchi adiacenti nello spazio degli indirizzi. L'elenco dei blocchi liberi è solo un elenco circolare di blocchi di memoria che all'inizio hanno alcuni dati amministrativi. Questo è anche il motivo per cui la gestione di elementi di memoria molto piccoli con lo standard malloc / free non è efficiente. Ogni blocco di memoria necessita di dati aggiuntivi e con dimensioni inferiori si verifica una maggiore frammentazione.

L'elenco libero è anche il primo posto che viene visualizzato da Malloc quando è necessaria una nuova porzione di memoria. Viene scansionato prima che richieda nuova memoria dal sistema operativo. Quando viene trovato un pezzo più grande della memoria necessaria, viene diviso in due parti. Uno viene restituito al chiamante, l'altro viene riportato nell'elenco gratuito.

Esistono diverse ottimizzazioni per questo comportamento standard (ad esempio per piccoli blocchi di memoria). Ma poiché malloc e free devono essere così universali, il comportamento standard è sempre il fallback quando le alternative non sono utilizzabili. Esistono anche ottimizzazioni nella gestione dell'elenco libero, ad esempio la memorizzazione dei blocchi in elenchi ordinati per dimensioni. Ma anche tutte le ottimizzazioni hanno i loro limiti.

Perché il tuo codice si arresta in modo anomalo:

Il motivo è che scrivendo 9 caratteri (non dimenticare il byte null finale) in un'area dimensionata per 4 caratteri, probabilmente sovrascriverai i dati amministrativi memorizzati per un altro blocco di memoria che risiede "dietro" il tuo blocco di dati ( poiché questi dati sono spesso memorizzati "davanti" ai blocchi di memoria). Quando libero prova quindi a mettere il tuo pezzo nella lista libera, può toccare questi dati amministrativi e quindi inciampare su un puntatore sovrascritto. Questo causerà l'arresto anomalo del sistema.

Questo è un comportamento piuttosto grazioso. Ho anche visto situazioni in cui un puntatore in fuga da qualche parte ha sovrascritto i dati nell'elenco di memoria libera e il sistema non si è arrestato immediatamente ma alcune subroutine in seguito. Anche in un sistema di media complessità tali problemi possono essere davvero molto difficili da eseguire il debug! Nel primo caso in cui sono stato coinvolto, ci sono voluti diversi giorni (un gruppo più ampio di sviluppatori) per trovare il motivo dell'incidente, poiché si trovava in una posizione completamente diversa da quella indicata dal dump della memoria. È come una bomba a orologeria. Sai, il tuo prossimo "libero" o "malloc" andrà in crash, ma non sai perché!

Questi sono alcuni dei peggiori problemi di C / C ++ e uno dei motivi per cui i puntatori possono essere così problematici.


63
Quindi molte persone non si rendono conto che free () potrebbe non restituire memoria al sistema operativo, è esasperante. Grazie per aver contribuito a illuminarli.
Artelius

Artelius: al contrario, la nuova volontà fa sempre?
Guillaume07

3
@ Guillaume07 Presumo che intendessi cancellare, non nuovo. No, non (necessariamente). elimina e libera (quasi) la stessa cosa. Ecco il codice che ognuno chiama in MSVC2013: goo.gl/3O2Kyu
Yay295

1
delete chiamerà sempre il distruttore, ma la memoria stessa potrebbe andare in una lista libera per una successiva allocazione. A seconda dell'implementazione, potrebbe anche essere la stessa lista libera usata da malloc.
David C.,

1
@Juergen Ma quando free () legge byte extra che contengono informazioni quanta memoria allocata da malloc, ottiene 4. Quindi quanto è successo l'arresto anomalo o quanto free () tocca i dati amministrativi?
Comportamento indefinito

56

Come dice aluser in questo thread del forum :

Il tuo processo ha una regione di memoria, dall'indirizzo x all'indirizzo y, chiamata heap. Tutti i tuoi dati malloc risiedono in quest'area. malloc () mantiene una struttura di dati, diciamo un elenco, di tutti i blocchi di spazio liberi nell'heap. Quando chiami malloc, cerca nell'elenco un pezzo abbastanza grande per te, restituisce un puntatore ad esso e registra il fatto che non è più libero così come è grande. Quando chiami free () con lo stesso puntatore, free () cerca quanto è grande quel pezzo e lo aggiunge alla lista di pezzi liberi (). Se chiami malloc () e non riesce a trovare un pezzo abbastanza grande nell'heap, utilizza brk () syscall per far crescere l'heap, ovvero aumentare l'indirizzo y e causare tutti gli indirizzi tra il vecchio y e il nuovo y per essere memoria valida. brk () deve essere un syscall;

malloc () dipende dal sistema / compilatore, quindi è difficile dare una risposta specifica. Fondamentalmente, tuttavia, tiene traccia di quale memoria è allocata e in base a come lo fa in modo che le chiamate gratuite possano fallire o avere successo.

malloc() and free() don't work the same way on every O/S.


1
Ecco perché si chiama comportamento indefinito. Un'implementazione potrebbe far volare i demoni dal naso quando chiami gratis dopo una scrittura non valida. Non si sa mai.
Braden Best

36

Una implementazione di malloc / free procede come segue:

  1. Ottieni un blocco di memoria dal sistema operativo tramite sbrk () (chiamata Unix).
  2. Crea un'intestazione e un piè di pagina attorno a quel blocco di memoria con alcune informazioni come dimensioni, autorizzazioni e dove si trovano il blocco successivo e precedente.
  3. Quando arriva una chiamata a malloc, viene fatto riferimento a un elenco che punta a blocchi della dimensione appropriata.
  4. Questo blocco viene quindi restituito e le intestazioni e i piè di pagina vengono aggiornati di conseguenza.

25

La protezione della memoria ha una granularità di pagina e richiederebbe l'interazione del kernel

Il tuo codice di esempio essenzialmente chiede perché il programma di esempio non intercetta, e la risposta è che la protezione della memoria è una funzione del kernel e si applica solo a intere pagine, mentre l'allocatore di memoria è una funzione di libreria e gestisce ... senza applicazione ... arbitrario blocchi di dimensioni che sono spesso molto più piccoli delle pagine.

La memoria può essere rimossa dal programma solo in unità di pagine e anche questo è improbabile che venga osservato.

calloc (3) e malloc (3) interagiscono con il kernel per ottenere memoria, se necessario. Ma la maggior parte delle implementazioni di free (3) non restituiscono memoria al kernel 1 , ma la aggiungono a un elenco gratuito che calloc () e malloc () consulteranno in seguito per riutilizzare i blocchi rilasciati.

Anche se un free () volesse restituire memoria al sistema, avrebbe bisogno di almeno una pagina di memoria contigua per consentire al kernel di proteggere effettivamente la regione, quindi rilasciare un piccolo blocco porterebbe a una modifica della protezione solo se fosse l' ultimo piccolo blocco in una pagina.

Quindi il tuo blocco è lì, seduto sulla lista libera. Puoi quasi sempre accedervi e memoria vicina come se fosse ancora allocata. C viene compilato direttamente in base al codice macchina e senza speciali disposizioni di debug non esistono controlli di integrità su carichi e negozi. Ora, se provi ad accedere a un blocco libero, il comportamento non è definito dallo standard al fine di non fare richieste irragionevoli agli implementatori di librerie. Se si tenta di accedere alla memoria libera o memoria fuori da un blocco allocato, ci sono varie cose che possono andare storte:

  • A volte gli allocatori mantengono blocchi di memoria separati, a volte usano un'intestazione che allocano appena prima o dopo (un "piè di pagina", immagino) il tuo blocco, ma potrebbero voler usare la memoria all'interno del blocco allo scopo di mantenere l'elenco libero collegati insieme. In tal caso, la lettura del blocco è OK, ma il suo contenuto potrebbe cambiare e la scrittura sul blocco potrebbe causare un malfunzionamento o l'arresto anomalo dell'allocatore.
  • Naturalmente, il tuo blocco potrebbe essere allocato in futuro, quindi è probabile che venga sovrascritto dal tuo codice o da una routine di libreria o con zero da calloc ().
  • Se il blocco viene riallocato, potrebbe anche essere cambiato la sua dimensione, nel qual caso verranno scritti ancora più collegamenti o inizializzazioni in vari punti.
  • Ovviamente puoi fare riferimento così lontano dall'intervallo che attraversi un confine di uno dei segmenti noti del kernel del tuo programma, e in questo caso intrappolerai.

Teoria dell'operazione

Quindi, lavorando all'indietro dal tuo esempio alla teoria generale, malloc (3) ottiene memoria dal kernel quando ne ha bisogno, e in genere in unità di pagine. Queste pagine sono divise o consolidate come richiesto dal programma. Malloc e free collaborano per mantenere una directory. Si fondono quando possibile blocchi liberi adiacenti per essere in grado di fornire blocchi di grandi dimensioni. La directory può o meno implicare l'utilizzo della memoria in blocchi liberati per formare un elenco collegato. (L'alternativa è un po 'più condivisa per la memoria e per il paging, e comporta l'allocazione di memoria specifica per la directory.) Malloc e free hanno poca o nessuna possibilità di imporre l'accesso ai singoli blocchi anche quando il codice di debug speciale e opzionale è compilato in il programma.


1. Il fatto che pochissime implementazioni di free () tentino di restituire memoria al sistema non è necessariamente dovuto al rallentamento degli implementatori. L'interazione con il kernel è molto più lenta della semplice esecuzione del codice della libreria e il vantaggio sarebbe ridotto. La maggior parte dei programmi ha un footprint di memoria costante o in aumento, quindi il tempo impiegato per analizzare l'heap alla ricerca di memoria restituibile sarebbe completamente sprecato. Altre ragioni includono il fatto che la frammentazione interna rende improbabile l'esistenza di blocchi allineati alla pagina ed è probabile che la restituzione di un blocco frammenti i blocchi su entrambi i lati. Infine, è probabile che i pochi programmi che restituiscono grandi quantità di memoria bypassino malloc () e semplicemente allochino e liberino comunque le pagine.


Buona risposta. Consiglierei l'articolo: Allocazione dinamica dello spazio di archiviazione: un sondaggio e una revisione critica di Wilson et al.
Goaler444

23

In teoria, malloc ottiene memoria dal sistema operativo per questa applicazione. Tuttavia, poiché potresti volere solo 4 byte e il sistema operativo deve funzionare su pagine (spesso 4k), malloc fa un po 'di più. Prende una pagina e inserisce le proprie informazioni in modo che possa tenere traccia di ciò che è stato allocato e liberato da quella pagina.

Quando si allocano 4 byte, ad esempio, malloc fornisce un puntatore a 4 byte. Quello che potresti non capire è che la memoria 8-12 byte prima dei tuoi 4 byte viene utilizzata da malloc per creare una catena di tutta la memoria che hai allocato. Quando chiami gratis, prende il tuo puntatore, esegue il backup su dove si trovano i dati e opera su quello.

Quando liberate memoria, malloc toglie quel blocco di memoria dalla catena ... e può o meno restituire quella memoria al sistema operativo. In tal caso, l'accesso alla memoria probabilmente non riuscirà, poiché il sistema operativo ti toglierà le autorizzazioni per accedere a tale posizione. Se malloc mantiene la memoria (perché ha altre cose allocate in quella pagina o per qualche ottimizzazione), allora l'accesso accadrà. È ancora sbagliato, ma potrebbe funzionare.

NOTA BENE: Quello che ho descritto è un'implementazione comune di malloc, ma non è l'unica possibile.


12

La tua linea strcpy tenta di memorizzare 9 byte, non 8, a causa del terminatore NUL. Richiama comportamenti indefiniti.

La chiamata a free può o meno bloccarsi. La memoria "dopo" i 4 byte della tua allocazione potrebbe essere usata per qualcos'altro dalla tua implementazione C o C ++. Se viene utilizzato per qualcos'altro, quindi scarabocchiare dappertutto causerà che "qualcos'altro" va storto, ma se non viene utilizzato per nient'altro, allora potresti capitarti di cavartela. "Farla franca" potrebbe sembrare buono, ma in realtà è male, poiché significa che il tuo codice sembrerà funzionare correttamente, ma in una corsa futura potresti non farcela.

Con un allocatore di memoria in stile debug, potresti scoprire che un valore di protezione speciale è stato scritto lì e che il controllo gratuito di tale valore e panico se non lo trova.

Altrimenti, potresti scoprire che i prossimi 5 byte includono parte di un nodo di collegamento che appartiene a qualche altro blocco di memoria che non è stato ancora assegnato. Liberare il blocco potrebbe comportare anche l'aggiunta di un elenco di blocchi disponibili e, poiché hai scarabocchiato nel nodo elenco, tale operazione potrebbe dereferenziare un puntatore con un valore non valido, causando un arresto anomalo.

Tutto dipende dall'allocatore di memoria: implementazioni diverse utilizzano meccanismi diversi.


12

Il funzionamento di malloc () e free () dipende dalla libreria di runtime utilizzata. In generale, malloc () alloca un heap (un blocco di memoria) dal sistema operativo. Ogni richiesta a malloc () quindi alloca un piccolo pezzo di questa memoria restituendo un puntatore al chiamante. Le routine di allocazione della memoria dovranno archiviare alcune informazioni aggiuntive sul blocco di memoria allocato per poter tenere traccia della memoria utilizzata e libera sull'heap. Queste informazioni sono spesso archiviate in pochi byte prima del puntatore restituito da malloc () e possono essere un elenco collegato di blocchi di memoria.

Scrivendo oltre il blocco di memoria allocato da malloc () molto probabilmente distruggerai alcune delle informazioni contabili del blocco successivo che potrebbe essere il blocco di memoria rimanente inutilizzato.

Un punto in cui il programma potrebbe anche arrestarsi in modo anomalo è quando si copiano troppi caratteri nel buffer. Se i caratteri aggiuntivi si trovano all'esterno dell'heap, è possibile che si verifichi una violazione di accesso mentre si sta tentando di scrivere nella memoria inesistente.


6

Questo non ha nulla a che fare specificamente con malloc e gratuito. Il programma mostra un comportamento indefinito dopo aver copiato la stringa - potrebbe bloccarsi in quel punto o in qualsiasi momento successivo. Ciò sarebbe vero anche se non avessi mai usato malloc e free e avessi allocato l'array char nello stack o staticamente.


5

malloc e free dipendono dall'implementazione. Un'implementazione tipica prevede il partizionamento della memoria disponibile in un "elenco libero", un elenco collegato di blocchi di memoria disponibili. Molte implementazioni lo dividono artificialmente in oggetti piccoli o grandi. I blocchi gratuiti iniziano con informazioni su quanto è grande il blocco di memoria e su dove si trova il prossimo, ecc.

Quando mallochi, un blocco viene estratto dalla lista libera. Quando sei libero, il blocco viene riportato nell'elenco gratuito. È probabile che, quando si sovrascrive la fine del puntatore, si sta scrivendo sull'intestazione di un blocco nell'elenco gratuito. Quando si libera la memoria, free () tenta di esaminare il blocco successivo e probabilmente finisce per colpire un puntatore che causa un errore del bus.


4

Bene dipende dall'implementazione dell'allocatore di memoria e dal sistema operativo.

In Windows, ad esempio, un processo può richiedere una pagina o più di RAM. Il sistema operativo quindi assegna tali pagine al processo. Questa non è, tuttavia, memoria allocata per l'applicazione. L'allocatore di memoria CRT contrassegnerà la memoria come un blocco "disponibile" contiguo. L'allocatore di memoria CRT eseguirà quindi l'elenco dei blocchi liberi e troverà il blocco più piccolo possibile che può utilizzare. Quindi prenderà tutto il blocco necessario e lo aggiungerà a un elenco "allocato". All'intestazione dell'attuale allocazione di memoria sarà collegata un'intestazione. Questa intestazione conterrà vari bit di informazione (potrebbe, ad esempio, contenere i blocchi allocati successivi e precedenti per formare un elenco collegato. Con ogni probabilità conterrà la dimensione dell'allocazione).

Free rimuoverà quindi l'intestazione e la aggiungerà nuovamente all'elenco di memoria libera. Se forma un blocco più grande con i blocchi liberi circostanti, questi verranno aggiunti insieme per dare un blocco più grande. Se un'intera pagina è ora libera, l'allocatore restituirà, molto probabilmente, la pagina al sistema operativo.

Non è un problema semplice. La parte dell'allocatore del sistema operativo è completamente fuori controllo. Ti consiglio di leggere qualcosa come Doug Lea's Malloc (DLMalloc) per capire come funzionerà un allocatore abbastanza veloce.

Modifica: il tuo arresto anomalo sarà causato dal fatto che scrivendo più grande dell'allocazione hai sovrascritto l'intestazione di memoria successiva. In questo modo, quando si libera, si confonde molto su ciò che è esattamente liberatorio e su come unirsi nel blocco seguente. Questo non può sempre causare un arresto immediato sul libero. Potrebbe causare un arresto in seguito. In generale, evitare di sovrascrivere la memoria!


3

Il programma si arresta in modo anomalo perché utilizza memoria che non ti appartiene. Può essere usato da qualcun altro o no - se sei fortunato a schiantarti, in caso contrario il problema potrebbe rimanere nascosto per lungo tempo e tornare indietro e morderti in seguito.

Per quanto riguarda l'implementazione malloc / gratuita, interi libri sono dedicati all'argomento. Fondamentalmente l'allocatore otterrebbe grossi blocchi di memoria dal sistema operativo e li gestirà per te. Alcuni dei problemi che un allocatore deve affrontare sono:

  • Come ottenere nuova memoria
  • Come memorizzarlo - (elenco o altra struttura, più elenchi per blocchi di memoria di dimensioni diverse e così via)
  • Cosa fare se l'utente richiede più memoria di quella attualmente disponibile (richiedere più memoria dal sistema operativo, unire alcuni dei blocchi esistenti, come unirli esattamente, ...)
  • Cosa fare quando l'utente libera memoria
  • Gli allocatori di debug possono darti un pezzo più grande di quello che hai richiesto e riempirlo di un modello di byte, quando liberi la memoria l'allocatore può controllare se scritto fuori dal blocco (cosa che probabilmente sta accadendo nel tuo caso) ...

2

È difficile da dire perché il comportamento effettivo è diverso tra diversi compilatori / runtime. Anche le build di debug / release hanno comportamenti diversi. Le build di debug di VS2005 inseriranno dei marker tra le allocazioni per rilevare il danneggiamento della memoria, quindi invece di un crash, affermerà in free ().


1

È anche importante rendersi conto che semplicemente spostando il puntatore di interruzione del programma con brke sbrknon allocare effettivamente la memoria, imposta semplicemente lo spazio degli indirizzi. Su Linux, ad esempio, la memoria sarà "supportata" da pagine fisiche effettive quando si accede a quell'intervallo di indirizzi, il che provocherà un errore di pagina e alla fine porterà il kernel a chiamare nell'allocatore di pagina per ottenere una pagina di supporto.

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.