realloc : prototype, fonctionnement
realloc() s'utilise après qu'on ait utilisé la malloc() ou calloc(), mais on peut aussi la rappeler plusieurs fois de suite (dans une boucle
for ou
while par exemple).
Elle sert à ré-attribuer de la mémoire à un pointeur mais pour une taille mémoire différente.
Il faut faire
#include <stdlib.h> pour pouvoir s'en servir.
Voici son prototype :
Code : C | void* realloc(void *ptr, size_t size);
|
Le premier argument est le pointeur sur lequel on désire effectuer l'opération, le deuxième argument est la taille de l'espace mémoire qu'on veut allouer.
realloc() modifie la taille de la zone mémoire précédemment attribuée à un pointeur soit par malloc(), soit par calloc() (la fonction utilisée avant l'appel de realloc()), soit par realloc() elle-même, et renvoie l'adresse de la nouvelle zone mémoire allouée.
Si la zone mémoire précédemment allouée peut être augmentée sans empiéter sur une zone mémoire utilisée pour autre chose, alors l'adresse mémoire renvoyée n'est pas modifiée (c'est la même que l'ancienne) mais le nombre de cases mémoires disponibles est modifié (c'était le but recherché).
En revanche, si en augmentant la zone mémoire initiale on déborde sur une zone mémoire déjà occupée, le système d'exploitation cherchera une autre adresse pour laquelle le nombre de cases mémoire nécessaires (le paramètre size) est disponible.
Comme ses demi-sœurs, elle renvoie un pointeur nul en cas d'échec. Il faut là aussi tester cette valeur si on ne veut pas risquer de faire planter notre programme.
un exemple
Code : C | char *maChaine = calloc(15, sizeof(char));
if (maChaine == NULL)
pb_memoire(); /* Cette fonction affiche un message d'erreur et termine le programme.
quelques instructions */
maChaine = realloc(maChaine, 20 * sizeof(char))
if (maChaine == NULL)
pb_memoire();
/* quelques instructions */
free(maChaine);
|
La ligne 6 de l'exemple demande à attribuer 20 cases mémoire pouvant contenir un char à maChaine et retourne l'adresse pour laquelle ces 20 cases mémoires sont disponibles à maChaine.
La fonction pb_memoire() ressemblerait à quelque chose comme :
Code : C | void pb_memoire(void)
{
printf("ERREUR : probleme de memoire !\n");
exit(EXIT_FAILURE);
}
|
De plus, en cas de changement d'adresse du pointeur, les éléments stockés précédemment en mémoire sont déplacés vers la nouvelle adresse. Donc, dans l'exemple précédent, les cases 0 à 14 contiennent les caractères qui on été stockés avant de faire appel à realloc().
Par conséquent, si on veut faire la même opération avec seulement les instructions malloc() et free(), c'est plus compliqué et cela donne :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 | char *maChaine = malloc(15 * sizeof(char)), *copieDeMaChaine = NULL;
/* 15 ou plus probablement la valeur d'une variable. */
int i;
if (maChaine == NULL)
pb_memoire();
for (i=0; i<15; ++i)
maChaine[i] = 0; /* On met tout à zéro, ce que fait calloc().
quelques instructions */
copieDeMaChaine = malloc(15 * sizeof(char));
if (copieDeMaChaine == NULL)
pb_memoire();
for (i = 0; i < 15; i++)
copieDeMaChaine[i] = maChaine[i]; /* On enregistre les données pour ne pas les perdre. */
free(maChaine); /* On libére la zone mémoire avant d'en acquérir une nouvelle. */
maChaine = malloc(20 * sizeof(char)); /* On demande plus de place en mémoire. */
if (maChaine == NULL)
pb_memoire();
for (i = 0; i < 15; i++)
maChaine[i] = copieDeMaChaine[i]; /* On récupère les valeurs qu'on a sauvegardées. */
free(copieDeMaChaine); /* On peut maintenant supprimer les données sauvegardées.
quelques instructions */
free(maChaine); /* On n'oublie pas de libérer la mémoire à la fin du programme ou de la fonction. */
|
J'ai parlé de concision pour calloc() tout à l'heure. Là, on est servi !
Dans ces exemples, on pourrait remplacer la boucle for() par la fonction strcpy(). Cela est vrai parce qu'on utilise des char mais on ne peut plus utiliser cette fonction dès lors qu'on manipule autre chose que des char, comme des int, des double etc. Donc l'utilisation de la boucle for() est une technique qui marchera dans tous les cas de figure.
La bonne utilisation de realloc
L'instruction ptr = realloc(ptr, nbElements * sizeof(typeElement)); présente un défaut :
Avant l'appel de realloc, le pointeur ptr pointe sur une zone valide de mémoire si on a appelé malloc ou calloc avant.
Si la ré-allocation échoue, realloc renvoie NULL, qui est affecté à ptr, or realloc préserve le bloc mémoire précédemment alloué, ce qui signifie que le bloc mémoire n'est pas libéré. En affectant NULL à ptr, on perd donc l'adresse de la zone mémoire allouée !
La solution consiste à créer deux pointeurs : ptr et ptr_realloc par exemple, et c'est à ptr_realloc qu'on affectera le résultat de realloc. Il suffit ensuite de tester sa valeur, et si elle n'est pas nulle, on la réaffecte à ptr.
Créons la fonction de réallocation sécurisée suivante :
Code : C | void* realloc_s (void **ptr, size_t taille)
{
void *ptr_realloc = realloc(*ptr, taille);
if (ptr_realloc != NULL)
*ptr = ptr_realloc;
/* Même si ptr_realloc est nul, on ne vide pas la mémoire. On laisse l'initiative au programmeur. */
return ptr_realloc;
}
|
Un exemple concret : réallocation sécurisée
Dans l'exemple suivant, j'ai créé un programme qui demande à l'utilisateur de rentrer une chaîne de caractère et un caractère. Dans cette chaîne de caractère, on va "supprimer" un caractère particulier. En réalité, on crée simplement un décalage en écrasant le caractère à supprimer par le suivant. La fonction deleteCharInString ne gère pas la mémoire. Il faut donc réajuster la taille de la mémoire pour correspondre parfaitement à la nouvelle vraie taille.
Code : C - exemple.c 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132 | #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define OS /* définir ici WINDOWS, LINUX ou MAC-OSX */
#define PAUSE puts("Appuyez sur entree pour continuer..."); \
viderBuffer(); /* En effet, viderBuffer permet également de faire une pause dans le programme ! */
char* deleteCharInString(char c, char *string);
void* realloc_s(void **ptr, size_t taille); /* déclaration de la fonction de réallocation sécurisée */
size_t rentrerChaine(char **tailleChaine); /* renvoie la taille de la chaine */
char rentrerCaractere(void);
void viderBuffer(void);
int main(int argc, char *argv[])
{
char *chaine, toDelete;
size_t tailleChaine;
puts("Entrez une chaine de caractere :");
tailleChaine = rentrerChaine(&chaine);
if (tailleChaine > 0) {
puts("Entrez le caractere de la chaine a supprimer :");
toDelete = rentrerCaractere();
printf("\nVous avez entre la chaine \"%s\" et le caractere '%c'.\n", chaine, toDelete);
/* On affiche à l'écran ce que l'utilisateur a rentré (éventuellement tronqué si c'était trop grand pour contenir
dans le tableau). */
deleteCharInString(toDelete, chaine);
realloc_s(&chaine, (strlen(chaine)+1) * sizeof(char)); /* La taille de la chaine a sûrement changé :
on ajuste la taille mémoire bien que ça ne soit pas indispensable, mais cela permet ici de réduire l'occupation en mémoire
de la chaîne. */
printf("\nLa chaine apres suppression de '%c' vaut maintenant :\n%s\n", toDelete, chaine);
}
free(chaine);
#ifdef WINDOWS
/*
* Pause pour pouvoir lire avant que la console ne disparaisse.
* Ceci est une version portable.
*/
PAUSE
#endif
return EXIT_SUCCESS;
}
char* deleteCharInString(char c, char *string)
{
size_t cpt = 0, count = 0;
if (c != '\0') { /* si on ne cherche pas à supprimer le caractère de fin de chaîne */
while (string[cpt] != c && string[cpt] != '\0')
++cpt;
/* On se positionne sur la première occurence du caractère à supprimer ou à la fin si le caractère à supprimer n'est pas dans la chaîne. */
if (string[cpt] == c) {
do ++count;
while (string[cpt+count] == c);
string[cpt] = string[cpt+count];
/* On remplace le caractère à supprimer par le suivant. */
while (string[cpt+count] != '\0') {
/* si on n'est pas à la fin de la chaîne, il faut recommencer.
On fait la même opération pour le caractère suivant. */
++cpt;
while (string[cpt+count] == c)
++count;
string[cpt] = string[cpt+count];
/* On affecte le prochain caractère qui ne soit pas à détruire. */
}
}
}
return string;
}
void* realloc_s(void **ptr, size_t taille)
{
void *ptr_realloc = realloc(*ptr, taille);
if (ptr_realloc != NULL)
*ptr = ptr_realloc;
return ptr_realloc;
}
void viderBuffer(void)
{
char poubelle;
do poubelle = getchar();
while (poubelle != '\n' && poubelle != EOF);
}
size_t rentrerChaine(char **chaine) /* alloue dynamiquement de la mémoire pour chaque caractère rentré : pas de gaspillage de mémoire. */
{
size_t tailleChaine = 1;
*chaine = malloc(sizeof(char));
if (*chaine != NULL) {
**chaine = getchar();
while (*chaine != NULL && (*chaine)[tailleChaine-1] != '\n' && (*chaine)[tailleChaine-1] != EOF)
if (realloc_s(chaine, ++tailleChaine * sizeof(char)))
(*chaine)[tailleChaine-1] = getchar();
else {
free(*chaine); /* la mémoire est pleine : on la libère. */
*chaine = NULL;
viderBuffer();
tailleChaine = 1;
}
if (*chaine != NULL)
(*chaine)[tailleChaine-1] = '\0';
}
return --tailleChaine;
}
char rentrerCaractere(void)
{
char input = getchar();
while (input == '\n') {
puts("Vous n'avez pas entre un caractere valide. Veuillez recommencer.");
input = getchar(); /* prend un nouveau caractere */
}
viderBuffer();
return input;
}
|
Ceci produit :
Code : Console | Entrez une chaine de caractere :
Ceci est une chaine de caractere quelconque. On va en supprimer tous les 'e'.
Entrez le caractere de la chaine a supprimer :
e
Vous avez entre la chaine "Ceci est une chaine de caractere quelconque. On va en supprimer tous les 'e'." et le caractere 'e'.
La chaine apres suppression de 'e' vaut maintenant :
Cci st un chain d caractr qulconqu. On va n supprimr tous ls ''. |