Quando dovrei usare malloc in C e quando no?


94

Capisco come funziona malloc (). La mia domanda è, vedrò cose come questa:

#define A_MEGABYTE (1024 * 1024)

char *some_memory;
size_t size_to_allocate = A_MEGABYTE;
some_memory = (char *)malloc(size_to_allocate);
sprintf(some_memory, "Hello World");
printf("%s\n", some_memory);
free(some_memory);

Ho omesso il controllo degli errori per brevità. La mia domanda è: non puoi semplicemente fare quanto sopra inizializzando un puntatore a un archivio statico in memoria? Forse:

char *some_memory = "Hello World";

A che punto hai effettivamente bisogno di allocare la memoria da solo invece di dichiarare / inizializzare i valori che devi conservare?


5
Ri: Ho omesso il controllo degli errori per brevità - sfortunatamente troppi programmatori omettono il controllo degli errori perché non si rendono conto che malloc()può fallire!
Andrew

Risposte:


132
char *some_memory = "Hello World";

sta creando un puntatore a una costante di stringa. Ciò significa che la stringa "Hello World" sarà da qualche parte nella parte di sola lettura della memoria e hai solo un puntatore ad essa. Puoi usare la stringa come di sola lettura. Non puoi modificarlo. Esempio:

some_memory[0] = 'h';

Chiede guai.

D'altro canto

some_memory = (char *)malloc(size_to_allocate);

sta allocando un array di caratteri (una variabile) e some_memory punta a quella memoria allocata. Ora questo array è sia in lettura che in scrittura. Ora puoi fare:

some_memory[0] = 'h';

e il contenuto dell'array cambia in "hello World"


19
Giusto per chiarire, per quanto mi piaccia questa risposta (ti ho dato +1), puoi fare lo stesso senza malloc () semplicemente usando un array di caratteri. Qualcosa come: char some_memory [] = "Hello"; some_memory [0] = 'W'; funzionerà anche.
randombits

19
Hai ragione. Ce la puoi fare. Quando usi malloc () la memoria viene allocata dinamicamente in fase di esecuzione, quindi non è necessario correggere la dimensione dell'array in fase di compilazione, inoltre puoi farlo crescere o rimpicciolire usando realloc () Nessuna di queste cose può essere fatta quando lo fai: char some_memory [] = "Ciao"; Anche se puoi modificare il contenuto dell'array, la sua dimensione è fissa. Quindi, a seconda delle esigenze, si utilizza una delle tre opzioni: 1) puntatore a char const 2) array allocato dinamicamente 3) dimensione fissa, tempo di compilazione allocato array.
codaddict

Per sottolineare che è di sola lettura, dovresti scrivere const char *s = "hi";Non è effettivamente richiesto dallo standard?
Till Theis,

@Till, no perché hai dichiarato un puntatore inizializzato all'indirizzo di base della stringa letterale "hi". s possono essere riassegnati perfettamente legalmente per puntare a un carattere non const. Se vuoi un puntatore costante a una stringa di sola lettura, hai bisogno diconst char const* s;
Rob11311

38

Per quell'esempio esatto, malloc è di scarsa utilità.

Il motivo principale per cui è necessario malloc è quando si dispone di dati che devono avere una durata diversa dall'ambito del codice. Il tuo codice chiama malloc in una routine, memorizza il puntatore da qualche parte e alla fine chiama free in una routine diversa.

Una ragione secondaria è che C non ha modo di sapere se c'è abbastanza spazio rimasto nello stack per un'allocazione. Se il tuo codice deve essere robusto al 100%, è più sicuro usare malloc perché il tuo codice può conoscere l'allocazione non riuscita e gestirla.


4
I cicli di vita della memoria e la relativa questione di quando e come risolverla sono un problema importante con molte librerie e componenti software comuni. Di solito hanno una regola ben documentata: "Se passi un puntatore a questa delle mie routine, devi averla assegnata. Ne terrò traccia e la libererò quando avrò finito. " Una fonte comune di bug fastidiosi è passare un puntatore alla memoria allocata staticamente a tale libreria. Quando la libreria tenta di liberarlo (), il programma va in crash. Recentemente ho passato molto tempo a correggere un bug come quello scritto da qualcun altro.
Bob Murphy,

Stai dicendo che l'unica volta che malloc () viene utilizzato praticamente, è quando c'è un segmento di codice che verrà chiamato più volte durante la vita del programma che verrà chiamato più volte e dovrà essere 'ripulito', poiché malloc () è accompagnato da free ()? Ad esempio, in un gioco come la ruota della fortuna, dove dopo aver indovinato e inserito l'input in un array di caratteri designato, che l'array di dimensioni malloc () può essere liberato per la prossima ipotesi?
Smith Will Suffice

La durata dei dati è infatti la vera ragione per utilizzare malloc. Supponiamo che un tipo di dati astratto sia rappresentato da un modulo, dichiari un tipo di elenco e routine per aggiungere / eliminare elementi dall'elenco. Questi valori degli elementi devono essere copiati nella memoria allocata dinamicamente.
Rob11311

@ Bob: quei brutti bug rendono la convenzione che l'allocatore libera la memoria di gran lunga superiore, dopotutto potresti riciclarla. Supponiamo di aver allocato la memoria con calloc per migliorare la località dei riferimenti, il che espone la natura danneggiata di quelle librerie, perché è necessario chiamare free solo una volta per l'intero blocco. Fortunatamente non ho dovuto usare librerie che specificano che la memoria deve essere 'malloc-ed' non è una tradizione POSIX e molto probabilmente sarebbe considerata un bug. Se "sanno" che devi usare malloc, perché la routine della libreria non lo fa per te?
Rob11311

17

malloc è uno strumento meraviglioso per allocare, riallocare e liberare memoria in fase di esecuzione, rispetto alle dichiarazioni statiche come il tuo esempio hello world, che vengono elaborate in fase di compilazione e quindi non possono essere modificate in dimensione.

Malloc è quindi sempre utile quando si ha a che fare con dati di dimensioni arbitrarie, come leggere il contenuto di file o gestire socket e non si è a conoscenza della lunghezza dei dati da elaborare.

Ovviamente, in un esempio banale come quello che hai fornito, malloc non è il magico "strumento giusto per il lavoro giusto", ma per casi più complessi (creare un array di dimensioni arbitrarie in fase di esecuzione per esempio), è l'unico modo per partire.


7

Se non conosci la dimensione esatta della memoria che devi usare, hai bisogno dell'allocazione dinamica ( malloc). Un esempio potrebbe essere quando un utente apre un file nella tua applicazione. Dovrai leggere il contenuto del file in memoria, ma ovviamente non conosci in anticipo la dimensione del file, poiché l'utente seleziona il file sul posto, in fase di esecuzione. Quindi fondamentalmente hai bisogno mallocquando non conosci in anticipo la dimensione dei dati con cui stai lavorando. Almeno questo è uno dei motivi principali per l'utilizzo malloc. Nel tuo esempio con una stringa semplice di cui conosci già la dimensione in fase di compilazione (in più non vuoi modificarla), non ha molto senso allocarla dinamicamente.


Un po 'fuori tema, ma ... devi stare molto attento a non creare perdite di memoria durante l'utilizzo malloc. Considera questo codice:

int do_something() {
    uint8_t* someMemory = (uint8_t*)malloc(1024);

    // Do some stuff

    if ( /* some error occured */ ) return -1;

    // Do some other stuff

    free(someMemory);
    return result;
}

Vedi cosa c'è di sbagliato in questo codice? C'è un'istruzione di ritorno condizionale tra malloce free. All'inizio potrebbe sembrare a posto, ma pensaci. Se c'è un errore, tornerai senza liberare la memoria che hai allocato. Questa è una fonte comune di perdite di memoria.

Ovviamente questo è un esempio molto semplice, ed è molto facile vedere l'errore qui, ma immagina centinaia di righe di codice disseminate di puntatori, mallocs, se freetutti i tipi di gestione degli errori. Le cose possono diventare davvero complicate molto velocemente. Questo è uno dei motivi per cui preferisco di gran lunga il C ++ moderno rispetto al C nei casi applicabili, ma questo è un argomento completamente diverso.

Quindi, ogni volta che lo usi malloc, assicurati sempre che la tua memoria sia il più probabile freepossibile.


Ottimo esempio!
Ben

6
char *some_memory = "Hello World";
sprintf(some_memory, "Goodbye...");

è illegale, le stringhe letterali lo sono const.

Questo allocherà un array di caratteri a 12 byte sullo stack o globalmente (a seconda di dove è dichiarato).

char some_memory[] = "Hello World";

Se vuoi lasciare spazio per ulteriori manipolazioni, puoi specificare che l'array dovrebbe essere di dimensioni maggiori. (Per favore, non mettere 1 MB in pila, però.)

#define LINE_LEN 80

char some_memory[LINE_LEN] = "Hello World";
strcpy(some_memory, "Goodbye, sad world...");
printf("%s\n", some_memory);

5

Un motivo per cui è necessario allocare la memoria è se si desidera modificarlo in fase di esecuzione. In tal caso, è possibile utilizzare un malloc o un buffer sullo stack. Il semplice esempio di assegnazione di "Hello World" a un puntatore definisce la memoria che "tipicamente" non può essere modificata in fase di esecuzione.

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.