Description des mécanismes d'allocation dynamique de mémoire en langage C

Romuald Perrot

Ce document a pour but de vous familiariser avec les allocations et libérations de mémoire de façon dynamique en langage C.

Votre avis et vos suggestions sur ce tutoriel m'intéressent ! Alors après votre lecture, n'hésitez pas : Commentez

- Titre : Description des mécanismes d'allocation dynamique de mémoire en langage C

- Auteur : Romuald Perrot

- Parution : 28 décembre 2005

- Mise à jour : 16 janvier 2005

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ®  Romuald Perrot. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Introduction

- 1. Allocation dynamique

- 1-1. Fonction malloc

- 1-1-1. Allocation dynamique de tableaux

- 1-2. Fonction calloc

- 2. Réallocation dynamique

- 3. Libération de mémoire

- 4. Quelques facilités de programmation

- 4-1. Récupération de la taille à allouer

- 4-2. Macros utiles

Introduction

L'allocation dynamique de mémoire permet la réservation d'un espace mémoire pour son programme au moment de son exécution. Ceci est à mettre en opposition avec l'allocation statique de mémoire. En effet, dans ce type d'allocation, la mémoire est réservée dès le début de l'exécution d'un bloc.

L'allocation dynamique intervient dans beaucoup de cas, en effet, une des raisons qui explique cela est le fait que l'on ne connaît pas forcément à l'avance le nombre d'objets ou la taille des objets que l'on va créer. Un exemple simple est l'utilisation d'une liste chaînée. Dans une telle structure, on peut ajouter à volonté des maillons dans notre liste sans se soucier du nombre déjà créé et d'une quelconque limite que l'on pourrait rencontrer dans le cas de l'utilisation d'un tableau. La seule limite à ce système d'allocation est en fait la mémoire disponible sur la machine exécutant le programme. En effet, si vous voulez demander un nouveau bloc mémoire alors qu'il n'y a plus de mémoire disponible (on considère que la mémoire physique et virtuelle sont saturées), l'allocation va échouer. Ce type d'erreur est à prendre en compte et nous verrons comment gérer cela.

Les fonctions qui seront décrites dans cet article appartiennent toutes au fichier d'en-tête suivant :stdlib.h .Il faudra donc ne pas oublier de l'inclure dans tous les fichiers utilisant ce mécanisme d'allocation de mémoire.

1 - Allocation dynamique

L'allocation dynamique de mémoire en langage C peut être réalisée via deux fonctions. La fonction malloc() et la fonction calloc().

1-1 - Fonction malloc

La fonction malloc() a la signature suivante :

void * malloc ( size_t t )

Cette fonction demande au système d'exploitation un bloc de taille t (en bytes) et renvoie un pointeur vers l'adresse du bloc alloué. S'il se produit une erreur, typiquement il n'y a plus de mémoire disponible, cette fonction renvoie la valeur NULL. La fonction retourne une adresse non typée, ceci n'est pas un problème, en effet, le type void * est compatible avec tous les pointeurs, par conséquent une conversion de type n'est pas obligatoire. Cependant, pour des raisons historiques, il était d'usage d'effectuer cette conversion sur le retour de la fonction dans le type de la variable qui stockera cette adresse. Ainsi, si on voulait allouer dynamiquement un entier, on faisait une conversion de type vers un pointeur d'entier. Ceci n'est cependant plus une chose à faire et nous nous efforcerons de ne plus le faire. Voici ce que cela peut donner pour un entier :

int * p;
p = malloc (sizeof(int));

Expliquons quelque peu ce petit exemple : tout d'abord, nous définissons un pointeur sur un entier (ici un pointeur sur un int). C'est ce pointeur qui va nous permettre de contenir l'adresse de l'élément qui va être alloué. En généralisant on peut dire que pour allouer dynamiquement un objet de type T, il faudra créer un pointeur sur un type T. Ensuite, nous tentons d'allouer la mémoire via la fonction malloc. nous donnons en paramètre de cette fonction, l'argument suivant : sizeof(int). Ceci nous permet de connaître la taille en octet à allouer. En effet, comme nous l'avons dit plus tôt dans cet article, la fonction malloc() alloue un bloc de taille t et non pas un bloc d'un type donné, cela est dû au fait que le système d'exploitation ne sais réserver qu'un espace mémoire, il ne sais pas si ce bloc va contenir un entier ou une structure beaucoup plus complexe. il faut donc indiquer la taille du bloc que l'on veut allouer. Ici, il s'agit tout simplement de la taille du type entier (dans notre cas du type int ). Comme il n'est pas toujours possible de connaître la taille en octet des objets que nous voulons allouer. L'opérateur sizeof nous simplifie alors le travail. Celle ci nous donne la taille en bytes d'un type (ou d'une variable) donné. Ainsi, la fonction malloc() connaît désormais la taille de ce qui doit être alloué. Si tout se passe bien, p devrait contenir une adresse mémoire indiquant où se situe le bloc que le système d'exploitation vient d'allouer.

Cependant, nous avons oublié quelque chose. En effet, nous sommes parti du principe que tout avait fonctionné correctement. Nous n'avons pas testé la valeur de retour de la fonction malloc().C'est celle-ci qui va nous permettre de vérifier que ladite fonction s'est correctement éxécutée. Comme nous l'avons dit plus tôt, si la fonction renvoie NULL, c'est qu'elle n'a pas réussi à allouer la mémoire. Si la valeur de retour n'est pas NULL, alors tout c'est bien passé. En fait, si la valeur n'est pas NULL, c'est que la fonction a renvoyé une adresse mémoire et donc que l'allocation a été faite.

Voici donc le premier exemple correct :

int * p;
p = malloc (sizeof(int));

if ( p == NULL )
{
fprintf(stderr,"Allocation impossible \n");
exit(EXIT_FAILURE);
}

Ainsi, maintenant, si l'allocation dynamique n'a pas pu avoir lieu, le programme affiche un message d'erreur et quitte en retournant la constante EXIT_FAILURE

Compliquons un petit peu notre exemple et créons une structure qui devra être allouée :

/* le type complexe */
struct complexe
{
double Re;
double Im;
};
/* une défition plus courte pour la structure */
typedef struct complexe Complexe;

Complexe * c;

c = malloc(sizeof(Complexe));

if ( c == NULL )
{
fprintf(stderr,"Allocation Impossible");
exit(EXIT_FAILURE);
}

L'exemple ne rajoute aucune difficulté, ici nous n'allouons pas un type atomique (entier, réel, caractère,...), mais une structure. Comme vous pouvez le constater, le schéma d'allocation est rigoureusement le même : on alloue un bloc de taille sizeof(struct complexe). Dans notre exemple, nous n'avons que deux membres dans notre structure mais le schéma serait le même si nous avions une structure beaucoup plus complexe.

1-1-1 - Allocation dynamique de tableaux

Maintenant, passons à un exemple un peu plus évolué, supposons que nous voulions allouer un tableau de 3 nombres complexes. Nous allons voir comment avec la fonction malloc nous pouvons résoudre ce problème.

La fonction malloc alloue un bloc de taille t. Un tableau n'est plus ni moins qu'une succession de blocs de même taille. Alors si nous voulons allouer un tableau de taille n, il faudra tout simplement allouer n blocs de taille t. Ainsi, plutôt que d'allouer un bloc de taille t, nous allouerons un bloc de taille n * t.

Voici donc comment nous allouons un tableau de 3 nombres complexes :

Complexe * tab;

tab = malloc (3 * sizeof(Complexe));

if( tab == NULL )
{
fprintf(stderr,"Allocation impossible");
exit(EXIT_FAILURE);
}

Ici, nous avons une petite question : nous avons alloué un tableau, mais quelle est donc la valeur de retour ? Tout simplement l'adresse du bloc alloué, c'est à dire le début du tableau. C'est donc un pointeur vers le premier élément du tableau qui est renvoyé.Ceci est donc conforme avec le fait qu'un tableau est égal (en terme de pointeur) au premier élément du tableau. (ie : tab == tab[0] )

Pour accéder à la deuxième case du tableau, il suffit d'ajouter la taille d'un bloc à l'adresse du premier élément(obtenue avec sizeof). Ainsi, nous obtiendrons l'adresse du deuxième élément. On peut également utiliser une autre méthode, utiliser l'arithmétique des pointeurs. En effet, ce mécanisme permet de se déplacer de blocs en blocs dans une suite de blocs.

Ainsi pour accéder aux trois cases de notre tableau on peut faire comme suit :

/* La première case */
(*tab).Re = 10.;
(*tab).Im = 10.;

/* La deuxième case */
(*(tab + 1)).Re = 15.;
(*(tab+ 1)).Im = 20.;

/* La troisième */
(*(tab + 2)).Re = 1.;
(*(tab + 2)).Im = 1.;

Le principe n'est pas si compliqué : on indique le décalage dans le bloc, c'est à dire le numéro de la case (en C les tableaux commencent à la case 0), puis on utilise l'opérateur de déréférencement pour accéder au contenu de la case.

Vous trouvez peut être cela compliqué et lourd à écrire ? Oui ça l'est, et par chance, le langage à tout prévu, c'est lui qui se charge de tout : vous n'avez donc pas à vous soucier des adresses et utiliser les mêmes écritures que lors des allocations statiques des tableaux : c'est à dire que vous pouvez procédez comme suit :

/* La première case */
tab[0].Re = 10.;
tab[0].Im = 10.;

/* La deuxième case */
tab[1].Re = 15.;
tab[1].Im = 20.;

/* La troisième */
tab[2].Re = 1.;
tab[2].Im = 1.;

Ainsi, c'est le compilateur qui se charge de tout : suivant le type il connaît la taille des blocs et en déduit automatiquement l'adresse des éléments. Ce qui facilite grandement le travail du programmeur, et permet de ne pas faire de différence à l'utilisation entre tableaux dynamiques et tableaux statiques.

1-2 - Fonction calloc

Revenons un peu sur l'allocation de notre tableau, comme nous l'avons vu précédemment, nous pouvons utiliser une autre fonction pour allouer de la mémoire : calloc(). Voici sa signature :

void * calloc (size_t n, size_t t)

La fonction alloue n blocs de taille t. elle est donc presque équivalente à l'appel suivant :

malloc( n * t );

La seule différence réside dans le contenu des cases qui sont allouée. En effet, avec malloc(), le contenu est totalement aléatoire tandis qu'avec calloc, les cases contiennent des valeurs nulles (tous les bits du bloc alloué sont mis à 0). Ceci est très utile pour initialiser rapidement un tableau de nombre. Si ce n'est ce point de détail, il n'y a aucune autre différence, et nous pourrons donc écrire notre code précédent de la façon suivante :

Complexe * p;

p = calloc (3 , sizeof(Complexe));

Pour des raisons de non redondance, nous ne remettons pas les tests de validité du pointeur, mais ils sont essentiels. Comme vous pouvez le constater, il n'y a aucune différence, en terme d'utilisation juste après le calloc.

Voyons un dernier exemple d'allocation dynamique avant de voir la réallocation. Notre dernier exemple d'allocation dynamique va nous permettre d'allouer un tableau à deux dimensions (mais le schéma serait le même quelque soit le nombre de dimensions). Cet exemple est tout simplement là pour vous montrer que l'on peut aussi allouer un espace mémoire pour des pointeurs, en effet, les pointeurs ne sont ni plus ni moins que des entiers (une adresse mémoire est identifée par un entier).

Voici donc comment on alloue un tableau de trois tableau de trois entier. En d'autres termes nous créons une matrice carrée d'entiers de taille 3 :

int **matrice , i;

matrice = malloc( 3 * sizeof(int*));

if( matrice == NULL )
{
fprintf(stderr,"Allocation impossible");
exit(EXIT_FAILURE);
}

for( i = 0 ; i < 3 ; i++ )
{
matrice[i] = calloc (3, sizeof(int));

if( matrice[i] == NULL )
{
fprintf(stderr,"Allocation impossible");
exit(EXIT_FAILURE);
}
}

/* remplissage */
for( i = 0 ; i<3 ; i++ )
matrice[i][i] = 1;

Comme vous pouvez le constater, lors du remplissage, nous ne remplissons que la diagonale, la fonction calloc() ayant mis à 0 toutes les cases lors de l'allocation.

A propos de ceci il faut faire attention à une chose : ce n'est pas parce que la fonction calloc() met les bits du bloc mémoire alloué à 0 que la valeur de la variable alloué est forcément égale à 0. La norme n'impose en effet aucune chose là dessus. Il se trouve qu'ici nous avons des entiers et dans la très grande majorité des cas le fonctionnement sera identique à ce que nous avons : les entiers valent 0. Cependant, il sera plus difficile de faire la même chose avec des nombres réels. On pourra avoir un nombre réel qui a tous ces bits à 0 de manière interne mais qui ne vaut pas forcément 0. Il faudra donc faire attention à ne pas trop se reposer sur calloc()

Sinon, à part ce petit point de détail, il n'y pas de grande difficulté, on commence par créer notre tableau de tableau via la fonction malloc(). On utilise la fonction malloc() ici mais on pourrai utiliser calloc(), c'est juste pour vous montrer que l'on peut utiliser les deux. Ensuite, pour chacune des cases de notre tableau, on crée un tableau d'entier via la fonction calloc(), ce qui nous permet de mettre toutes les cases du tableau à la valeur 0. Nous verrons dans la dernière partie quelques macros utiles qui allégeront le code pour ne pas avoir à tester les valeurs de retour des fonctions d'allocations.

2 - Réallocation dynamique

Passons maintenant à la dernière fonction d'allocation : la fonction realloc() voici sa signature :

void * realloc ( void * base , size_t t )

La fonction realloc() tente de redimensionner un bloc mémoire donné à la fonction via son adresse base avec une nouvelle taille t. Dans le cadre d'un tableau il faudra donc redimensionner notre tableau avec une nouvelle taille étant donnée par la formule suivante : n*taille_bloc. Comme d'habitude, si une erreur se produit, la fonction retourne le pointeur NULL. Si en revanche, tout s'est bien passé, la fonction renvoie un pointeur vers l'adresse du nouveau bloc réalloué. La fonction réalloue le bloc mémoire tout en gardant le contenu de ce qui se trouvait dans le bloc précédent. La fonction ne fait donc qu'un changement de taille. Ceci est donc utile pour un tableau dynamique : en effet, on peut ajouter ou enlever une case à la fin du tableau sans le modifier. Rien de tel qu'un exemple pour illustrer comment fonctionne la fonction :

int * tabentier;
/* Création d'un tableau de 3 entiers */
tabentier = calloc ( 3 , sizeof(int) );

tabentier[0] = 1;
tabentier[1] = 2;
tabentier[2] = 3;

/* Ajout d'un element au tableau */
tabentier = realloc (tabentier, 4 * sizeof(int) );

tabentier[3] = 4;

for ( i = 0 ; i < 3 ; i++ )
{
printf(" tabentier[%d] = %d \n", i , tabentier[i] );
}

Le résultat de l'exécution est le suivant :

tabentier[0] = 1
tabentier[1] = 2
tabentier[2] = 3
tabentier[3] = 4

Le programme ne fait pas les tests d'erreur en cas d'allocation ou de réallocations impossibles, il faudrait les mettre pour éviter les plantages sournois dans nos programmes. Nous allons voir d'ailleur dans la suite qu'il est fortement conseillé de le faire avec la fonction realloc(). Mais pour l'instant, supposons que tout se soit bien passé.

Revenons en à notre programme, il commence par créer un tableau de trois entiers via la fonction calloc(). Ensuite, nous affectons des valeurs à chacune des cases du tableau. Juste après intervient la fonction realloc(), celle ci réalloue le bloc mémoire contenant notre tableau en changeant sa taille. Celle ci est : 4 * sizeof (int) , ce qui veut dire un bloc de taille 4 fois la taille d'un bloc d'entier. Il faut faire attention ici, il ne faut surtout pas mettre seulement 4 dans la nouvelle taille sinon, la fonction realloc() va réallouer notre tableau avec une taille de 4 octets . Et nous aurons des erreurs d'exécution par la suite. Enfin, nous remplissons le dernier élément puis nous affichons le tableau. Cet exemple montre bien que la fonction réalloc recopie notre tableau, et modifie sa taille . Dans l'exemple précédent, nous avons réalloué notre tableau avec une taille supérieure à celle d'origine mais on peut tout aussi bien réallouer avec une taille inférieure. Ceci aura pour effet de supprimer une ou plusieurs case(s) du tableau.

Maintenant que nous avons expliqué comment fonctionnait la fonction realloc(), supposons que tout ne se déroule pas correctement. Par exemple il est impossible de réallouer le tableau. La fonction réalloc va donc nous renvoyer le pointeur NULL. Comme nous avons fait une affection de notre ancien tableau alors nous perdons tous ce qui y était (il s'agit en fait d'une fuite mémoire : le contenu de notre ancien tableau existerai toujours physiquement mais il serait impossible d'y accéder). Il faut donc remplacer notre fonction de réallocation par celle ci :

int * temp;
temp = realloc (tabentier, 4 * sizeof(int) );

if ( temp == NULL )
{
fprintf(stderr,"Reallocation impossible");
free(tabentier);
exit(EXIT_FAILURE);
}
else
{
tabentier = temp;
}

Voici donc comment gérer les réallocations mémoire de manière correcte. Détaillons un petit peu. Tout d'abord, au lieu d'affecter le retour de la fonction realloc à notre tableau originel, nous affectons le retour à une variable temporaire. Celle ci, si elle vaut NULL après l'appel à la fonction realloc(), indique une erreur de réallocation. Si tel est le cas, nous affichons un message d'erreur, nous libérons la mémoire occupée par notre tableau originel et nous quittons le programme avec le code d'erreur EXIT_FAILURE. Si la réallocation s'est bien passé, c'est à dire que realloc() n'a pas retourné NULL, alors, on affecte le résultat de la fonction à notre tableau (exactement comme nous avions fait au début.

Au tout début de cette partie, j'ai dis que la fonction realloc() était la dernière fonction d'allocation alors que pour l'instant nous avons vu que la fonction ne pouvait intervenir qu'après une allocation via malloc() ou calloc(). Ceci n'est pas tout à fait vrai, en effet, la fonction realloc() peut faire office de fonction d'allocation dans un cas particulier.

Ce cas particulier est tout simplement le cas où la fonction reçoit en adresse de base le pointeur NULL. Dans ce cas alors la fonction se comporte exactement comme la fonction malloc(). Ainsi notre premier exemple d'utilisation de realloc aurait pu s'écrire comme ceci :

int * tabentier = NULL ,*temp;
/* Création d'un tableau de 3 entiers */
tabentier = realloc ( tabentier , 3 * sizeof(int) );

if ( tabentier == NULL )
{
fprintf(stderr,"Allocation impossible");
exit(EXIT_FAILURE);
}

tabentier[0] = 1;
tabentier[1] = 2;
tabentier[2] = 3;

/* Redimensionnement*/
temp = realloc (tabentier, 4 * sizeof(int ) );

if ( temp == NULL )
{
fprintf(stderr,"Reallocation impossible");
free(tabentier);
exit(EXIT_FAILURE);
}
else
{
tabentier = temp;
}

/* Ajout d'un élement */
tabentier[3] = 4;

for ( i = 0 ; i < 3 ; i++ )
{
printf(" tabentier[%d] = %d \n", i , tabentier[i] );
}

3 - Libération de mémoire

Voilà nous en avons fini pour les fonctions d'allocation de mémoire. Nous allons voir la fonction qui effectue l'opération inverse : la libération de mémoire. La libération de mémoire est extrêmement simple : elle se fait via la fonction free(). Voici sa signature :

void free(void * bloc)

La fonction libère un bloc mémoire précédemment alloué via les fonctions malloc() ou calloc(). La libération de mémoire ne devrait pas poser de problème.

Si vous tentez de libérer un pointeur NULL la fonction ne fera strictement rien. En revanche, si vous tenez de libérer un bloc qui a précédement été désalloué, le comportement de la fonction est alors indéterminé. Il faut donc forcer le pointeur que l'on vient de libérer à la valeur NULL. Ainsi si on tente de le relibérer, il ne se passera rien du tout.

Voici donc un petit exemple d'utilisation de free :

int * entier;

entier = malloc (sizeof(int));
if( entier == NULL )
{
fprintf(stderr,"Allocation impossible");
}
else
{
/* affectation d'une valeur et affichage */
*entier = 3;
printf("%d",*entier);

/* libération */
free(entier);
entier = NULL;
}

Voilà donc ce cours exemple, qui alloue dynamiquement un entier, lui affecte la valeur 3, l'affiche et enfin le libère. A noter que nous avons forcé entier à devenir NULL en toute fin du programme. La fonction free ne mettant pas à NULL les pointeurs après libération de mémoire, nous forçons donc le pointeur à NULL. Ceci est très important dans la mesure où c'est la seule solution pour savoir si le pointeur pointe sur une zone mémoire valide ou non. Comme vous avez pu le constater, la fonction free() est très simple d'utilisation, mais derrière sa simplicité relative, elle cache des difficultés. En effet, la fonction libère un bloc précédemment alloué par malloc() ou calloc(), mais pas les blocs plus imbriqués.

Pour illustrer notre propos, reprenons notre exemple de la matrice, et libérons la matrice à la fin :

int ** matrice,i;         /* un tableau à deux dimension */

/*allocation d'un tableau de trois tableaux d'entiers */
matrice = malloc ( 3 * sizeof(int *) );

for ( i = 0 ; i < 3 ; i ++ )
{
/* allocation d'un tableau de tableau */
matrice[i] = calloc ( 3 , sizeof(int) );
}

/*remplissage d'une matrice diagonale*/
for ( i = 0 ; i < 3 ; i++ )
{
matrice[i][i] = 1;
}

free(matrice);
matrice = NULL;

Voici typiquement l'exemple ce ce qu'il ne faut pas faire. En effet, dans ce genre de cas, nous allons laisser des cases mémoires allouées que nous ne pourrons pas récupérer.

Détaillons : la fonction free() libère le bloc alloué par malloc() ou calloc(), donc dans notre cas, la fonction free() va libérer l'allocation faite avec malloc(). En revanche, les allocations faîtes avec calloc() ne seront pas libérées, ceci entraine donc des pertes de mémoire quasi irrécupérables. Pour libérer notre matrice, il faut donc faire comme suit :

for ( i = 0 ; i<3 ; i++)
{
free(matrice[i]);
matrice[i] = NULL ;
}
free(matrice);
matrice = NULL;

Dans ce cas, nous avons bien libéré toute notre matrice.

Voilà donc qui clos notre article sur l'allocation et la désallocation de mémoire. A titre de petit bonus, nous allons voir dans la partie suivante, quelques astuces qui permettent de simplifier la vie du programmeur.

4 - Quelques facilités de programmation

Nous allons voir deux choses qui vont surement simplifier les choses. La première est la récupération de la taille d'un bloc mémoire à allouer sans avoir besoin de connaître le type. La deuxième facilité sera l'utilisation de macros qui vérifieront la validité des allocations mémoires.

4-1 - Récupération de la taille à allouer

Lorsque vous allouez un pointeur d'entier, vous effectuez ceci :

long *pl = malloc( sizeof(long) );

Ceci est correct et c'est comme ceci que nous l'avons fait jusqu'à présent. Cependant, si vous devez changer le type qui doit être alloué dynamiquement, vous devrez à la fois changer le type du pointeur et le type qui est utilisé par l'opérateur sizeof. On peut se demander s'il n'y a pas moyen de faire plus simple. Et bien oui, il suffit tout simplement de donner à sizeof le pointeur que l'on veut allouer : voici ce que celà donne :

long *pl = malloc ( sizeof *pl );

Par conséquent, si vous désirez allouer un entier long non signé vous n'aurez qu'à modifer le type du pointeur comme ceci :

unsigned long *pl = malloc ( sizeof *pl );

Ainsi, on a plus qu'une seule modification à apporter.

4-2 - Macros utiles

La première macro est une macro pour remplacer la fonction malloc(). Vous n'aurez qu'à donner le pointeur qui vous sert à l'allocation, le type que vous voulez allouer et le nombre d'élément à allouer. La fonction teste la valeur de retour de l'allocation, et en cas d'erreur, elle vous fourni un message d'erreur contenant le nom du fichier dans lequel s'est produite l'erreur et le numéro de la ligne ou s'est produite l'erreur. Attention cependant, les macros suivantes sont ici pour vous indiquer la marche à suivre, elles ne sont pas totalement correcte, en effet, celles ci ne gèrent pas les effets de bord et ne tolère que les instructions simples. C'est à dire que pour allouer un bloc de taille n, n ne doit pas pas être un calcul mais une valeur.

Voici donc la macro :

#define MALLOC(_p,_t,_n) do{ \
(_p) = malloc ( (_n) * sizeof( (_t) )); \
if( (_p) == NULL ) \
{ \
fprintf(stderr,"Allocation impossible dans le fichier :%s ligne : %s",__FILE__,__LINE__);\
exit(EXIT_FAILURE); \
} \
}while(0)

La deuxième macro s'occupe de la fonction calloc() elle effectue la même chose que pour la fonction précédente :

#define CALLOC(_p,_t,_n) do{ \
(_p) = calloc ( (_n) , sizeof( (_t) ) ); \
if( (_p) == NULL ) \
{ \
fprintf(stderr,"Allocation impossible dans le fichier :%s ligne : %s",__FILE__,__LINE__);\
exit(EXIT_FAILURE); \
} \
}while(0)

La troisième macro réalise une réallocation mémoire pour un tableau, la macro prend en paramètre le tableau originel, le type des éléments et la nouvelle taille.

#define REALLOC(_p,_t,_n) do{ \
(_t) * temp; \
temp = realloc ( (_p) , (_n) * sizeof( (_t) ) ); \
if( temp == NULL ) \
{ \
fprintf(stderr,"Allocation impossible dans le fichier :%s ligne : %s",__FILE__,__LINE__);\
free( (_p) ); \
exit(EXIT_FAILURE); \
} \
else \
{ \
(_p) = temp; \
} \
}while(0)

Enfin voici une macro pour la libération de mémoire :

#define FREE(_p) do{ \
free( (_p) ); \
(_p) = NULL; \
}while(0)

Voilà donc pour ces petites macros, voyons un exemple qui illustre leur utilisation :

int ** matrice , i;         /* un tableau à deux dimension */

/*allocation d'un tableau de trois tableaux d'entiers */
MALLOC(matrice,int*,3);

for ( i = 0 ; i < 3 ; i ++ )
{
/* allocation d'un tableau de tableau */
CALLOC(matrice[i],int,3);
}

/*remplissage d'une matrice diagonale*/
for ( i = 0 ; i < 3 ; i++ )
{
matrice[i][i] = 1;
}

for(i = 0 ; i<3 ; i++)
{
FREE(matrice[i]);
}
FREE(matrice);

Les macros allègent donc votre code en évitant d'écrire des tests pour vérifier la bonne exécution des allocations ou désallocations. Mais attention cependant, pour vos programmes, ne les utilisez pas en l'état. (il faudrait gérer les effets de bord mais ce n'est pas le sujet de cet article).