Description des mécanismes d'allocation dynamique de mémoire en langage C
Date de publication : 28/12/2005 , Date de mise à jour : 16/01/2005
Par
Romuald Perrot (Cyber-Avenue)
Ce document a pour but de vous familiariser avec les allocations et libérations de
mémoire de façon dynamique en langage C.
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 :
struct complexe
{
double Re;
double Im;
};
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 :
tab[0].Re = 10.;
tab[0].Im = 10.;
tab[1].Re = 15.;
tab[1].Im = 20.;
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 :
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);
}
}
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;
tabentier = calloc ( 3 , sizeof(int) );
tabentier[0] = 1;
tabentier[1] = 2;
tabentier[2] = 3;
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;
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;
temp = realloc (tabentier, 4 * sizeof(int ) );
if ( temp == NULL )
{
fprintf(stderr,"Reallocation impossible");
free(tabentier);
exit(EXIT_FAILURE);
}
else
{
tabentier = temp;
}
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 :
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
{
*entier = 3;
printf("%d",*entier);
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;
matrice = malloc ( 3 * sizeof(int *) );
for ( i = 0 ; i < 3 ; i ++ )
{
matrice[i] = calloc ( 3 , sizeof(int) );
}
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;
MALLOC(matrice,int*,3);
for ( i = 0 ; i < 3 ; i ++ )
{
CALLOC(matrice[i],int,3);
}
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).
 
|