Ricerca…


introduzione

Per la gestione della memoria allocata dinamicamente, la libreria C standard fornisce le funzioni malloc() , calloc() , realloc() e free() . In C99 e aligned_alloc() successive, c'è anche aligned_alloc() . Alcuni sistemi forniscono anche alloca() .

Sintassi

  • void * aligned_alloc (size_t alignment, size_t size); / * Solo dopo C11 * /
  • void * calloc (size_t nelements, size_t size);
  • void free (void * ptr);
  • void * malloc (size_t size);
  • void * realloc (void * ptr, size_t size);
  • void * alloca (size_t size); / * da alloca.h, non standard, non portatile, pericoloso. * /

Parametri

nome descrizione
dimensione ( malloc , realloc e aligned_alloc ) dimensione totale della memoria in byte. Per aligned_alloc la dimensione deve essere un multiplo integrale di allineamento.
dimensione ( calloc ) dimensione di ogni elemento
Nelements numero di elementi
PTR puntatore alla memoria allocata precedentemente restituita da malloc , calloc , realloc o aligned_alloc
allineamento allineamento della memoria allocata

Osservazioni

C11

Notare che aligned_alloc() è solo definito per C11 o successivo.

Sistemi come quelli basati su POSIX forniscono altri modi per allocare memoria allineata (ad es. posix_memalign() ) e hanno anche altre opzioni di gestione della memoria (ad esempio mmap() ).

Liberare memoria

È possibile rilasciare memoria allocata dinamicamente chiamando free () .

int *p = malloc(10 * sizeof *p); /* allocation of memory */
if (p == NULL) 
{
    perror("malloc failed");
    return -1;
}

free(p); /* release of memory */
/* note that after free(p), even using the *value* of the pointer p
   has undefined behavior, until a new value is stored into it. */

/* reusing/re-purposing the pointer itself */
int i = 42;
p = &i; /* This is valid, has defined behaviour */

La memoria puntata da p viene recuperata (sia dall'implementazione di libc o dal sistema operativo sottostante) dopo la chiamata a free() , quindi l'accesso a quel blocco di memoria liberato tramite p porterà a un comportamento indefinito . I puntatori che fanno riferimento a elementi di memoria che sono stati liberati vengono comunemente chiamati puntatori penzolanti e presentano un rischio per la sicurezza. Inoltre, lo standard C afferma che anche l' accesso al valore di un puntatore pendente ha un comportamento indefinito. Si noti che il puntatore p può essere riproposto come mostrato sopra.

Si noti che è possibile chiamare free() sui puntatori che sono stati restituiti direttamente dalle funzioni malloc() , calloc() , realloc() e aligned_alloc() o dove la documentazione indica che la memoria è stata allocata in quel modo (funzioni come strdup () sono esempi notevoli). Liberare un puntatore che è,

  • ottenuto usando l'operatore & su una variabile, o
  • nel mezzo di un blocco assegnato,

è vietato. Tale errore di solito non verrà diagnosticato dal compilatore, ma condurrà l'esecuzione del programma in uno stato indefinito.

Esistono due strategie comuni per prevenire tali casi di comportamento non definito.

Il primo e preferibile è semplice: lo stesso p cessa di esistere quando non è più necessario, ad esempio:

if (something_is_needed())
{

    int *p = malloc(10 * sizeof *p);
    if (p == NULL) 
    {
        perror("malloc failed");
        return -1;
    }

    /* do whatever is needed with p */

    free(p);
}

Chiamando free() direttamente prima della fine del blocco contenitore (cioè il } ), p stesso cessa di esistere. Il compilatore darà un errore di compilazione su ogni tentativo di usare p dopo quello.

Un secondo approccio è anche quello di invalidare il puntatore stesso dopo aver rilasciato la memoria a cui punta:

free(p);
p = NULL;     // you may also use 0 instead of NULL

Argomenti per questo approccio:

  • Su molte piattaforme, un tentativo di dereferenziare un puntatore nullo causerà un arresto anomalo istantaneo: errore di segmentazione. Qui, otteniamo almeno una traccia stack che punta alla variabile che è stata utilizzata dopo essere stata liberata.

    Senza impostare il puntatore su NULL abbiamo il puntatore che penzola. Molto probabilmente il programma si bloccherà ancora, ma in seguito, perché la memoria a cui punta il puntatore verrà automaticamente danneggiata. Tali bug sono difficili da rintracciare perché possono causare uno stack di chiamate completamente estraneo al problema iniziale.

    Questo approccio segue quindi il concetto di fail-fast .

  • È sicuro liberare un puntatore nullo. Lo standard C specifica che free(NULL) non ha alcun effetto:

    La funzione libera fa sì che lo spazio puntato da ptr sia deallocato, cioè reso disponibile per un'ulteriore allocazione. Se ptr è un puntatore nullo, non si verifica alcuna azione. Altrimenti, se l'argomento non corrisponde a un puntatore precedentemente restituito dalla funzione calloc , malloc o realloc , o se lo spazio è stato deallocato da una chiamata a free o realloc , il comportamento non è definito.

  • A volte il primo approccio non può essere utilizzato (ad esempio, la memoria viene allocata in una funzione e rilasciata molto più tardi in una funzione completamente diversa)

Allocazione della memoria

Assegnazione standard

Le funzioni di allocazione della memoria dinamica C sono definite nell'intestazione <stdlib.h> . Se si desidera allocare lo spazio di memoria per un oggetto in modo dinamico, è possibile utilizzare il seguente codice:

int *p = malloc(10 * sizeof *p);
if (p == NULL) 
{
    perror("malloc() failed");
    return -1;
}

Questo calcola il numero di byte che dieci int occupano in memoria, quindi richiede molti byte da malloc e assegna il risultato (cioè l'indirizzo iniziale del blocco di memoria appena creato usando malloc ) a un puntatore denominato p .

È buona pratica usare sizeof per calcolare la quantità di memoria da richiedere poiché il risultato di sizeof è definito dall'implementazione (eccetto per i tipi di carattere , che sono char , signed char e unsigned char , per cui sizeof è definito per dare sempre 1 ).

Poiché malloc potrebbe non essere in grado di soddisfare la richiesta, potrebbe restituire un puntatore nullo. È importante controllare questo per impedire tentativi successivi di dereferenziare il puntatore nullo.

La memoria allocata dinamicamente usando malloc() può essere ridimensionata usando realloc() o, quando non è più necessaria, rilasciata usando free() .

In alternativa, dichiarando int array[10]; allocherebbe la stessa quantità di memoria. Tuttavia, se è dichiarato all'interno di una funzione senza la parola chiave static , sarà utilizzabile solo all'interno della funzione in cui è dichiarato e delle funzioni che chiama (poiché la matrice verrà allocata nello stack e lo spazio verrà rilasciato per il riutilizzo quando la funzione ritorna). In alternativa, se è definito con static all'interno di una funzione o se è definito al di fuori di qualsiasi funzione, la sua durata è la durata del programma. I puntatori possono anche essere restituiti da una funzione, tuttavia una funzione in C non può restituire un array.

Memoria azzerata

La memoria restituita da malloc potrebbe non essere inizializzata ad un valore ragionevole, e bisogna fare attenzione a azzerare la memoria con memset o copiare immediatamente un valore appropriato in essa. In alternativa, calloc restituisce un blocco della dimensione desiderata in cui tutti i bit sono inizializzati su 0 . Questo non deve essere uguale alla rappresentazione di zero virgola mobile o di una costante puntatore nullo.

int *p = calloc(10, sizeof *p);
if (p == NULL) 
{
    perror("calloc() failed");
    return -1;
}

Una nota su calloc : la maggior parte delle implementazioni (comunemente usate) ottimizzerà calloc() per le prestazioni, quindi sarà più veloce di chiamare malloc() , quindi memset() , anche se l'effetto netto è identico.

Memoria allineata

C11

C11 ha introdotto una nuova funzione aligned_alloc() che alloca lo spazio con l'allineamento specificato. Può essere utilizzato se la memoria da allocare è necessaria per essere allineata a determinati limiti che non possono essere soddisfatti da malloc() o calloc() . malloc() funzioni malloc() e calloc() allocano memoria che è adeguatamente allineata per qualsiasi tipo di oggetto (cioè l'allineamento è alignof(max_align_t) ). Ma con aligned_alloc() possono essere richiesti allineamenti maggiori.

/* Allocates 1024 bytes with 256 bytes alignment. */
char *ptr = aligned_alloc(256, 1024);
if (ptr) {
    perror("aligned_alloc()");
    return -1;
}
free(ptr);

Lo standard C11 impone due restrizioni: 1) la dimensione (secondo argomento) richiesta deve essere un multiplo integrale dell'allineamento (primo argomento) e 2) il valore dell'allineamento dovrebbe essere un allineamento valido supportato dall'implementazione. Il mancato rispetto di uno di questi risultati comporta un comportamento indefinito .

Riallocazione della memoria

Potrebbe essere necessario espandere o ridurre lo spazio di archiviazione del puntatore dopo aver allocato memoria ad esso. La funzione void *realloc(void *ptr, size_t size) rilascia il vecchio oggetto puntato da ptr e restituisce un puntatore a un oggetto che ha le dimensioni specificate dalla size . ptr è il puntatore a un blocco di memoria precedentemente assegnato con malloc , calloc o realloc (o un puntatore nullo) per essere riallocato. Il contenuto massimo possibile della memoria originale è conservato. Se la nuova dimensione è più grande, qualsiasi memoria aggiuntiva oltre la vecchia dimensione non è inizializzata. Se la nuova dimensione è più breve, il contenuto della parte ristretta viene perso. Se ptr è NULL, un nuovo blocco viene allocato e un puntatore ad esso viene restituito dalla funzione.

#include <stdio.h>
#include <stdlib.h>
 
int main(void)
{
    int *p = malloc(10 * sizeof *p);
    if (NULL == p) 
    {
        perror("malloc() failed");
        return EXIT_FAILURE;
    }
 
    p[0] = 42;
    p[9] = 15;

    /* Reallocate array to a larger size, storing the result into a
     * temporary pointer in case realloc() fails. */
    {
        int *temporary = realloc(p, 1000000 * sizeof *temporary);

        /* realloc() failed, the original allocation was not free'd yet. */
        if (NULL == temporary)
        {
            perror("realloc() failed");
            free(p); /* Clean up. */
            return EXIT_FAILURE;
        }      

        p = temporary;
    }

    /* From here on, array can be used with the new size it was 
     * realloc'ed to, until it is free'd. */

    /* The values of p[0] to p[9] are preserved, so this will print:
       42 15
    */
    printf("%d %d\n", p[0], p[9]);

    free(p);

    return EXIT_SUCCESS;
}

L'oggetto riallocato può avere o meno lo stesso indirizzo di *p . Pertanto è importante acquisire il valore di ritorno da realloc che contiene il nuovo indirizzo se la chiamata ha esito positivo.

Assicurati di assegnare il valore di ritorno di realloc ad un temporary invece dell'originale p . realloc restituirà null in caso di errore, che sovrascriverebbe il puntatore. Ciò perderebbe i tuoi dati e creerebbe una perdita di memoria.

Matrici multidimensionali di dimensioni variabili

C99

Poiché C99, C ha matrici di lunghezza variabile, VLA, quelle matrici di modelli con limiti noti solo al momento dell'inizializzazione. Mentre devi stare attento a non allocare VLA troppo grande (potrebbero distruggere il tuo stack), usare i puntatori a VLA e usarli in sizeof espressioni va bene.

double sumAll(size_t n, size_t m, double A[n][m]) {
    double ret = 0.0;
    for (size_t i = 0; i < n; ++i)
       for (size_t j = 0; j < m; ++j)
          ret += A[i][j]
    return ret;
}

int main(int argc, char *argv[argc+1]) {
   size_t n = argc*10;
   size_t m = argc*8;
   double (*matrix)[m] = malloc(sizeof(double[n][m]));
   // initialize matrix somehow
   double res = sumAll(n, m, matrix);
   printf("result is %g\n", res);
   free(matrix);
}

Qui matrix è un puntatore a elementi di tipo double[m] , e il sizeof espressione con double[n][m] assicura che contiene lo spazio per n tali elementi.

Tutto questo spazio è assegnato in modo contiguo e può quindi essere deallocato da una singola chiamata a free .

La presenza di VLA nella lingua influisce anche sulle possibili dichiarazioni di matrici e puntatori nelle intestazioni di funzione. Ora, è consentita un'espressione generale intera all'interno [] dei parametri dell'array. Per entrambe le funzioni le espressioni in [] usano i parametri che sono stati dichiarati prima nella lista dei parametri. Per sumAll queste sono le lunghezze che il codice utente si aspetta per la matrice. Come per tutti i parametri di funzione dell'array in C, la dimensione più interna viene riscritta su un tipo di puntatore, quindi questo è equivalente alla dichiarazione

  double sumAll(size_t n, size_t m, double (*A)[m]);

Cioè, n non fa realmente parte dell'interfaccia della funzione, ma le informazioni possono essere utili per la documentazione e potrebbero anche essere utilizzate dai limiti che controllano i compilatori per avvisare dell'accesso fuori dai limiti.

Probabilmente, per main , l'espressione argc+1 è la lunghezza minima che lo standard C prescrive per l'argomento argv .

Si noti che ufficialmente il supporto VLA è facoltativo in C11, ma non conosciamo alcun compilatore che implementa C11 e che non li ha. Puoi testare con la macro __STDC_NO_VLA__ se devi.

realloc (ptr, 0) non è equivalente a free (ptr)

realloc è concettualmente equivalente a malloc + memcpy + free sull'altro puntatore.

Se la dimensione dello spazio richiesto è zero, il comportamento di realloc è definito dall'implementazione. Questo è simile per tutte le funzioni di allocazione della memoria che ricevono un parametro di size del valore 0 . Tali funzioni possono infatti restituire un puntatore non nullo, ma non deve mai essere dereferenziato.

Quindi, realloc(ptr,0) non è equivalente a free(ptr) . Esso può

  • essere un'implementazione "pigra" e solo tornare ptr
  • free(ptr) , assegna un elemento fittizio e restituiscilo
  • free(ptr) e restituisce 0
  • basta restituire 0 per errore e non fare nient'altro.

Quindi, in particolare, questi ultimi due casi non sono distinguibili dal codice dell'applicazione.

Ciò significa che realloc(ptr,0) potrebbe non essere realmente libero / deallocare la memoria, e quindi non dovrebbe mai essere usato come sostituto free .

Gestione della memoria definita dall'utente

malloc() chiama spesso funzioni del sistema operativo sottostante per ottenere pagine di memoria. Ma non c'è nulla di speciale nella funzione e può essere implementato in C diritta dichiarando una grande matrice statica e assegnandole da essa (c'è una leggera difficoltà nell'assicurare un corretto allineamento, in pratica l'allineamento a 8 byte è quasi sempre adeguato).

Per implementare uno schema semplice, un blocco di controllo viene memorizzato nell'area della memoria immediatamente prima del puntatore da restituire dalla chiamata. Ciò significa che free() può essere implementato sottraendo dal puntatore restituito e leggendo le informazioni di controllo, che è tipicamente la dimensione del blocco più alcune informazioni che gli consentono di essere rimesso nella lista libera - un elenco collegato di blocchi non allocati.

Quando l'utente richiede un'allocazione, la ricerca della lista libera viene eseguita fino a quando non viene trovato un blocco di dimensioni identiche o maggiori dell'importo richiesto, quindi se necessario viene diviso. Questo può portare alla frammentazione della memoria se l'utente sta facendo continuamente molte allocazioni e libera da dimensioni imprevedibili e ad intervalli imprevedibili (non tutti i programmi reali si comportano in questo modo, lo schema semplice è spesso adeguato per i piccoli programmi).

/* typical control block */
struct block
{
   size_t size;         /* size of block */
   struct block *next;  /* next block in free list */ 
   struct block *prev;  /* back pointer to previous block in memory */
   void *padding;       /* need 16 bytes to make multiple of 8 */
}

static struct block arena[10000]; /* allocate from here */
static struct block *firstfree;

Molti programmi richiedono un numero elevato di allocazioni di piccoli oggetti della stessa dimensione. Questo è molto facile da implementare. Basta usare un blocco con un puntatore successivo. Quindi se è richiesto un blocco di 32 byte:

union block
{
   union block * next;
   unsigned char payload[32];
}  

static union block arena[100];
static union block * head; 
void init(void)
{
    int i;
    for (i = 0; i < 100 - 1; i++)
        arena[i].next = &arena[i + 1];
    arena[i].next = 0; /* last one, null */
    head = &block[0];
}
 
void *block_alloc()
{
    void *answer = head;
    if (answer)
        head = head->next;
    return answer;
}

void block_free(void *ptr)
{
    union block *block = ptr;
    block->next = head;
    head - block;
}

Questo schema è estremamente veloce ed efficiente e può essere reso generico con una certa perdita di chiarezza.

alloca: alloca la memoria sullo stack

alloca : l' alloca è menzionata solo qui per motivi di completezza. È interamente non portatile (non coperto da nessuno degli standard comuni) e ha un numero di funzioni potenzialmente pericolose che lo rendono non sicuro per l'inconsapevole. Il codice C moderno dovrebbe sostituirlo con Array a lunghezza variabile (VLA).

Pagina manuale

#include <alloca.h>
// glibc version of stdlib.h include alloca.h by default

void foo(int size) {
    char *data = alloca(size);
    /*
      function body;
    */
    // data is automatically freed
}

Assegna memoria al frame dello stack del chiamante, lo spazio a cui fa riferimento il puntatore restituito viene liberato automaticamente al termine della funzione chiamante.

Sebbene questa funzione sia utile per la gestione automatica della memoria, tenere presente che la richiesta di allocazioni elevate potrebbe causare un sovraccarico dello stack e che non è possibile utilizzare free con la memoria allocata con alloca (che potrebbe causare più problemi con l'overflow dello stack).

Per questo motivo non è raccomandato l'uso di alloca all'interno di un ciclo né una funzione ricorsiva.

E poiché la memoria è free 'su funzione restituita non è possibile restituire il puntatore come risultato di una funzione ( il comportamento sarebbe indefinito ).

Sommario

  • chiamare identico a malloc
  • automaticamente liberato al ritorno della funzione
  • incompatibile con le funzioni free , realloc ( comportamento non definito )
  • il puntatore non può essere restituito come risultato di una funzione ( comportamento non definito )
  • dimensione di allocazione limitata dallo spazio di stack, che (sulla maggior parte delle macchine) è molto più piccola dello spazio heap disponibile per l'uso da malloc()
  • evitare l'uso di alloca() e VLA (array di lunghezza variabile) in una singola funzione
  • alloca() non è così portatile come malloc() et al

Raccomandazione

  • Non utilizzare alloca() nel nuovo codice
C99

Alternativa moderna

void foo(int size) {
    char data[size];
    /*
      function body;
    */
    // data is automatically freed
}

Funziona dove alloca() fa e funziona in luoghi in cui non è alloca() (loop interni, ad esempio). Presuppone un'implementazione C99 o un'implementazione C11 che non definisce __STDC_NO_VLA__ .



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow