Recherche…


Introduction

Pour gérer la mémoire allouée dynamiquement, la bibliothèque C standard fournit les fonctions malloc() , calloc() , realloc() et free() . En C99 et aligned_alloc() ultérieures, il y a aussi aligned_alloc() . Certains systèmes fournissent également alloca() .

Syntaxe

  • void * alignment_alloc (alignement size_t, taille size_t); / * Seulement depuis C11 * /
  • void * calloc (size_t nelements, size_t size);
  • vide gratuit (annulation * ptr);
  • void * malloc (taille_taille);
  • void * realloc (void * ptr, size_t size);
  • void * alloca (taille_taille); / * from alloca.h, pas standard, pas portable, dangereux. * /

Paramètres

prénom la description
taille ( malloc , realloc et aligned_alloc ) taille totale de la mémoire en octets. Pour aligned_alloc la taille doit être un multiple entier de l'alignement.
taille ( calloc ) taille de chaque élément
éléments nombre d'éléments
ptr pointeur sur la mémoire allouée précédemment renvoyée par malloc , calloc , realloc ou aligned_alloc
alignement alignement de la mémoire allouée

Remarques

C11

Notez que aligned_alloc() est uniquement défini pour C11 ou version ultérieure.

Des systèmes tels que ceux basés sur POSIX fournissent d'autres moyens d'allouer de la mémoire alignée (par exemple, posix_memalign() ), et ont également d'autres options de gestion de la mémoire (par exemple, mmap() ).

Libérer de la mémoire

Il est possible de libérer de la mémoire allouée dynamiquement en appelant 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 mémoire pointée par p est récupérée (soit par l'implémentation de la libc ou par le système d'exploitation sous-jacent) après l'appel à free() , donc l'accès à ce bloc de mémoire libéré via p entraîne un comportement indéfini . Les pointeurs qui référencent des éléments de mémoire libérés sont communément appelés pointeurs , et présentent un risque de sécurité. De plus, le standard C stipule que même accéder à la valeur d'un pointeur en suspens a un comportement indéfini. Notez que le pointeur p lui-même peut être réutilisé comme indiqué ci-dessus.

Veuillez noter que vous ne pouvez appeler free() sur les pointeurs renvoyés directement par les fonctions malloc() , calloc() , realloc() et aligned_alloc() , ou lorsque la documentation vous indique que la mémoire a été allouée de cette manière (fonctions comme strdup () sont des exemples notables). Libérer un pointeur qui est,

  • obtenu en utilisant l'opérateur & sur une variable, ou
  • au milieu d'un bloc alloué,

est interdit. Une telle erreur ne sera généralement pas diagnostiquée par votre compilateur mais conduira l'exécution du programme dans un état indéfini.

Il existe deux stratégies courantes pour prévenir de tels cas de comportement indéfini.

Le premier et préférable est simple: p lui-même cesse-t-il d'exister lorsqu'il n'est plus nécessaire, par exemple:

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);
}

En appelant free() directement avant la fin du bloc contenant (c'est-à-dire le } ), p cesse d'exister. Le compilateur donnera une erreur de compilation sur toute tentative d'utilisation de p après cela.

Une deuxième approche consiste à invalider le pointeur lui-même après avoir libéré la mémoire sur laquelle il pointe:

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

Arguments pour cette approche:

  • Sur de nombreuses plates-formes, une tentative de déréférencement d'un pointeur nul provoquera un blocage instantané: erreur de segmentation. Ici, nous obtenons au moins une trace de pile pointant vers la variable utilisée après avoir été libérée.

    Si vous ne définissez pas le pointeur sur NULL nous avons un pointeur en attente. Le programme risque encore de se bloquer, mais plus tard, car la mémoire sur laquelle pointe le pointeur sera corrompue. Ces bogues sont difficiles à détecter car ils peuvent entraîner une pile d'appels sans aucun rapport avec le problème initial.

    Cette approche suit donc le concept rapide .

  • Vous pouvez libérer un pointeur nul. Le standard C spécifie que free(NULL) n'a aucun effet:

    La fonction libre provoque la désallocation de l’espace pointé par ptr, c’est-à-dire qu’elle est disponible pour une allocation ultérieure. Si ptr est un pointeur nul, aucune action ne se produit. Sinon, si l'argument ne correspond pas à un pointeur précédemment renvoyé par la fonction calloc , malloc ou realloc , ou si l'espace a été libéré par un appel à free ou realloc , le comportement n'est pas défini.

  • Parfois, la première approche ne peut pas être utilisée (par exemple, la mémoire est allouée dans une fonction et libérée beaucoup plus tard dans une fonction complètement différente)

Allouer de la mémoire

Allocation Standard

Les fonctions d'allocation de mémoire dynamique C sont définies dans l'en-tête <stdlib.h> . Si l'on souhaite allouer dynamiquement de l'espace mémoire pour un objet, le code suivant peut être utilisé:

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

Cela calcule le nombre d'octets que dix int s occupent en mémoire, puis demande que le nombre d' octets de malloc et affecte le résultat (c. -à l'adresse de départ du morceau de mémoire qui vient d'être créé à l' aide malloc ) à un pointeur nommé p .

Il est recommandé d'utiliser sizeof pour calculer la quantité de mémoire à demander, car le résultat de sizeof est défini par l'implémentation (sauf pour les types de caractères char , signed char et unsigned char , pour lesquels sizeof est toujours défini sur 1 ).

Comme malloc peut ne pas être en mesure de traiter la demande, il peut renvoyer un pointeur null. Il est important de vérifier cela pour empêcher les tentatives ultérieures de déréférencer le pointeur null.

La mémoire allouée dynamiquement à l'aide de malloc() peut être redimensionnée à l'aide de realloc() ou, lorsqu'elle n'est plus nécessaire, libérée à l'aide de free() .

Sinon, déclarez int array[10]; allouerait la même quantité de mémoire. Cependant, s'il est déclaré dans une fonction sans le mot-clé static , il ne sera utilisable que dans la fonction dans laquelle il est déclaré et dans les fonctions qu'il appelle (car le tableau sera alloué sur la pile et l'espace sera libéré pour être réutilisé). la fonction retourne). Sinon, si elle est définie avec static à static intérieur d'une fonction, ou si elle est définie en dehors d'une fonction, sa durée de vie correspond à la durée de vie du programme. Les pointeurs peuvent également être renvoyés par une fonction, mais une fonction dans C ne peut pas renvoyer un tableau.

Zéro mémoire

La mémoire renvoyée par malloc ne peut pas être initialisée à une valeur raisonnable, et il faut veiller à mettre la mémoire à zéro avec memset ou à y copier immédiatement une valeur appropriée. calloc renvoie également un bloc de la taille souhaitée où tous les bits sont initialisés à 0 . Ce n'est pas nécessairement la même chose que la représentation du zéro à virgule flottante ou d'une constante de pointeur nul.

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

Une note sur calloc : La plupart des implémentations (couramment utilisées) vont optimiser calloc() pour les performances, donc ce sera plus rapide que d'appeler malloc() , puis memset() , même si l'effet net est identique.

Mémoire Alignée

C11

C11 a introduit une nouvelle fonction aligned_alloc() qui alloue de l'espace avec l'alignement donné. Il peut être utilisé si la mémoire à attribuer doit être alignée à certaines limites qui ne peuvent pas être satisfaites par malloc() ou calloc() . malloc() et calloc() allouent de la mémoire convenablement alignée pour tout type d'objet (l'alignement est donc alignof(max_align_t) ). Mais avec aligned_alloc() des alignements plus importants peuvent être demandés.

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

Le standard C11 impose deux restrictions: 1) la taille (deuxième argument) demandé doit être un multiple entier de l’ alignement (premier argument) et 2) la valeur de l’ alignement doit être un alignement valide pris en charge par l’implémentation. Ne pas répondre à l'un d'eux entraîne un comportement indéfini .

Réaffecter la mémoire

Vous devrez peut-être agrandir ou réduire l'espace de stockage du pointeur après lui avoir affecté de la mémoire. La fonction void *realloc(void *ptr, size_t size) l'ancien objet pointé par ptr et renvoie un pointeur sur un objet ayant la taille spécifiée par size . ptr est le pointeur sur un bloc de mémoire précédemment alloué avec malloc , calloc ou realloc (ou un pointeur nul) à réallouer. Le contenu maximal possible de la mémoire d'origine est préservé. Si la nouvelle taille est plus grande, toute mémoire supplémentaire au-delà de l'ancienne taille n'est pas initialisée. Si la nouvelle taille est plus courte, le contenu de la partie réduite est perdu. Si ptr est NULL, un nouveau bloc est alloué et un pointeur sur celui-ci est renvoyé par la fonction.

#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'objet réalloué peut ou non avoir la même adresse que *p . Par conséquent, il est important de capturer la valeur de retour de realloc qui contient la nouvelle adresse si l'appel est réussi.

Assurez-vous d'attribuer la valeur de retour de realloc à un p temporary au lieu de l'original. realloc renverra null en cas de défaillance, ce qui écraserait le pointeur. Cela perdrait vos données et créerait une fuite de mémoire.

Tableaux multidimensionnels de taille variable

C99

Depuis C99, C possède des tableaux de longueur variable, VLA, qui modélisent les tableaux avec des limites connues uniquement au moment de l'initialisation. Bien qu'il faille faire attention à ne pas allouer de VLA trop volumineux (ils pourraient casser votre pile), utiliser des pointeurs vers VLA et les utiliser dans des expressions de sizeof est bien.

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);
}

Ici , la matrix est un pointeur sur des éléments de type à double[m] , et la sizeof expression avec double[n][m] assure qu ' elle contient un espace pour n tels éléments.

Tout cet espace est alloué de manière contiguë et peut ainsi être désalloué par un seul appel à free .

La présence de VLA dans le langage affecte également les déclarations possibles de tableaux et de pointeurs dans les en-têtes de fonction. Maintenant, une expression d'entier général est autorisée dans les [] des paramètres du tableau. Pour les deux fonctions, les expressions dans [] utilisent des paramètres déclarés auparavant dans la liste des paramètres. Pour sumAll ce sont les longueurs que le code utilisateur attend pour la matrice. Comme pour tous les paramètres de fonction de tableau dans C, la dimension la plus interne est réécrite dans un type de pointeur, ce qui équivaut à la déclaration

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

En d'autres termes, n ne fait pas vraiment partie de l'interface de la fonction, mais les informations peuvent être utiles pour la documentation et peuvent également être utilisées par les compilateurs pour vérifier les accès hors limites.

Semblablement, pour main , l'expression argc+1 est la longueur minimale que le standard C prescrit pour l'argument argv .

Notez que officiellement, le support VLA est facultatif dans C11, mais nous ne connaissons aucun compilateur qui implémente C11 et qui ne les possède pas. Vous pouvez tester avec la macro __STDC_NO_VLA__ si vous devez.

realloc (ptr, 0) n'est pas équivalent à free (ptr)

realloc est conceptuellement équivalent à malloc + memcpy + free sur l'autre pointeur.

Si la taille de l'espace demandé est égale à zéro, le comportement de realloc est défini par l'implémentation. Ceci est similaire pour toutes les fonctions d'allocation de mémoire qui reçoivent un paramètre de size de la valeur 0 . De telles fonctions peuvent en fait renvoyer un pointeur non nul, mais cela ne doit jamais être déréférencé.

Ainsi, realloc(ptr,0) n’est pas équivalent à free(ptr) . Cela pourrait

  • être une implémentation "paresseuse" et renvoyer juste ptr
  • free(ptr) , allouer un élément factice et renvoyer cette
  • free(ptr) et retour 0
  • retourne juste 0 pour échec et ne fais rien d'autre.

Ainsi, en particulier, les deux derniers cas ne peuvent être distingués par le code d'application.

Cela signifie que realloc(ptr,0) peut ne pas libérer / désallouer la mémoire, et ne devrait donc jamais être utilisé comme remplacement free .

Gestion de la mémoire définie par l'utilisateur

malloc() appelle souvent les fonctions sous-jacentes du système d'exploitation pour obtenir des pages de mémoire. Mais la fonction n'a rien de particulier et peut être implémentée dans straight C en déclarant un grand tableau statique et en l'allouant (il est difficile de garantir un alignement correct, en pratique, l'alignement sur 8 octets est presque toujours suffisant).

Pour implémenter un schéma simple, un bloc de contrôle est stocké dans la région de mémoire immédiatement avant le pointeur à renvoyer de l'appel. Cela signifie que free() peut être implémenté en soustrayant du pointeur renvoyé et en lisant les informations de contrôle, ce qui correspond généralement à la taille du bloc et à certaines informations lui permettant de revenir dans la liste libre - une liste liée de blocs non alloués.

Lorsque l'utilisateur demande une allocation, la liste libre est recherchée jusqu'à ce qu'un bloc de taille identique ou supérieure au montant demandé soit trouvé, puis si nécessaire, il est divisé. Cela peut entraîner une fragmentation de la mémoire si l'utilisateur effectue continuellement de nombreuses allocations et libère une taille imprévisible et à des intervalles imprévisibles (tous les programmes réels ne se comportent pas comme cela, le système simple est souvent adapté aux petits programmes).

/* 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;

De nombreux programmes nécessitent un grand nombre d'attributions de petits objets de même taille. C'est très facile à mettre en œuvre. Utilisez simplement un bloc avec un pointeur suivant. Donc, si un bloc de 32 octets est requis:

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;
}

Ce système est extrêmement rapide et efficace et peut être générique avec une certaine perte de clarté.

alloca: allouer de la mémoire sur la pile

Avertissement: alloca n'est mentionné ici que pour être complet. Il est entièrement non portable (non couvert par les normes communes) et comporte un certain nombre de caractéristiques potentiellement dangereuses qui le rendent inoffensif pour les non-avertis. Le code C moderne devrait le remplacer par des tableaux à longueur variable (VLA).

Page de manuel

#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
}

Allouer de la mémoire sur le cadre de la pile de l'appelant, l'espace référencé par le pointeur renvoyé est automatiquement libre lorsque la fonction de l'appelant est terminée.

Bien que cette fonction soit pratique pour la gestion automatique de la mémoire, sachez que demander une allocation importante peut provoquer un débordement de pile et que vous ne pouvez pas utiliser free mémoire allouée avec alloca (ce qui pourrait causer davantage de débordement de pile).

Pour cette raison, il n'est pas recommandé d'utiliser alloca dans une boucle ni une fonction récursive.

Et comme la mémoire est free au retour de fonction, vous ne pouvez pas renvoyer le pointeur en tant que résultat de fonction ( le comportement serait indéfini ).

Résumé

  • appel identique à malloc
  • automatiquement libéré lors du retour de la fonction
  • incompatible avec free fonctions realloc free ( comportement indéfini )
  • le pointeur ne peut pas être renvoyé en tant que résultat de fonction ( comportement non défini )
  • taille d'allocation limitée par l'espace de pile, qui (sur la plupart des machines) est beaucoup plus petite que l'espace de tas disponible pour l'utilisation par malloc()
  • éviter d'utiliser alloca() et VLAs (tableaux de longueur variable) dans une seule fonction
  • alloca() n'est pas aussi portable que malloc() et al

Recommandation

  • N'utilisez pas alloca() dans le nouveau code
C99

Alternative moderne.

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

Cela fonctionne là où alloca() fonctionne, et fonctionne dans les endroits où alloca() ne fonctionne pas (dans les boucles, par exemple). Il suppose une implémentation C99 ou une implémentation C11 qui ne définit pas __STDC_NO_VLA__ .



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow