« Programmation C/Gestion de la mémoire » : différence entre les versions

Contenu supprimé Contenu ajouté
→‎<code>realloc</code> : corrections et complément
Ligne 127 :
* En cas d'échec, cette fonction ne libère pas l'espace mémoire actuel, et retourne une adresse nulle.
 
Notez bien que <tt>realloc</tt> ne peut que modifier des espaces mémoires qui été alloués par <tt>malloc</tt>, <tt>calloc</tt>, ou <tt>realloc</tt>. En effet, autoriser <tt>realloc</tt> à manipuler des espaces mémoires qui ne sont pas issus des fonctions de la bibliothèque standard pourrait causer des erreurs, ou des incohérences graves de l'état du processus. En particulier, les tableaux, automatiques comme statiques, ne peuvent être passés à <tt>realloc</tt>, comme illustré par le code suivant :
<pre>/* Ce code contient une erreur volontaire. */
void f(void)
{
int tab[10];
/* La boucle... */
int *ptr = realloc(tab, 20 * sizeof(int));
/* ... */
}
</pre>
Lorsque <tt>realloc</tt> reçoit la valeur de <tt>tab</tt>, qui est un pointeur sur le premier élément (i.e. <tt>&tab[0]</tt>), il ne peut la traiter, et le comportement est indéfini. Sur cet exemple, il est facile de voir l'erreur, mais dans l'exemple suivant, la situation est plus délicate :
<pre>
#include <stdint.h>
#include <stdlib.h>
 
/* 'double' essaye de doubler l'espace mémoire pointé par ptr.
*
* En cas de succès, la valeur renvoyée est un pointeur vers le nouvel espace mémoire, et l'ancienne
* valeur de ptr est invalide.
* En cas d'échec, l'espace pointé par ptr n'est pas modifié, et la valeur NULL est renvoyée.
*/
void *double(void *ptr, size_t n)
{
ifvoid (ptr*tmp == NULL);
if ((ptr != NULL) && (n != 0) && (n <= SIZE_MAX / 2))
{
tmp = realloc(ptr, 2 * n);
}
return tmp;
}
</pre>
La fonction <tt>double</tt> en elle-même ne comporte pas d'erreur, mais elle peut causer des plantages suivant la valeur de <tt>ptr</tt> qui lui est passée. Pour éviter des erreurs, il faudrait que la documentation de la fonction précise les contraintes sur la valeur de <tt>ptr</tt>... et que les programmeurs qui l'utilisent y fassent attention.
 
On peut aussi noter que, quand <tt>realloc</tt> réussit, le pointeur renvoyé peut très bien être égal au pointeur initial, ou lui être différent.
En particulier, il n'y a aucune garantie que, quand on diminue la taille de la zone mémoire, il le fasse « sur place ». C'est très probable, car c'est ce qui est le plus facile et rapide à faire du point de vue de l'implémentation, mais rien ne l'empêche par exemple de chercher un autre espace mémoire disponible qui aurait exactement la taille voulue, au lieu de garder la zone mémoire initiale.
 
==== Gestion d'erreur ====
Pour gérer correctement le cas d'échec, on ne peut faire ainsi:
<pre>/* Ce code contient une erreur volontaire. */
Ligne 167 ⟶ 204 :
* lit sur une entrée quelconque (par exemple <tt>stdin</tt>) un entier, et renvoie 1 si la lecture s'est bien passée ;
* renvoie 0 si aucune valeur n'est présente sur l'entrée, ou en cas d'erreur.
Cette fonction est utilisée pour construire un tableau d'entiers issus de cette entrée. Comme on ne sait à l'avance combien d'éléments on va recevoir, on alloue d'abord un tableau à 10 éléments par <tt>malloc</tt>, puis on augmente la taille dud'un tableau au fur et à mesure avec <tt>realloc</tt>.
<pre>/* Fonction qui utilise 'lire_entree' pour lire un nombre indéterminé
* d'entiers.
Ligne 177 ⟶ 214 :
{
/* On initialise le traitement */
size_t max = 100; /* Nombre d'éléments utilisables */
size_t i = 0; /* Nombre d'éléments utilisés */
int *ptr = NULL; /* Pointeur vers le tableau dynamique */
int valeur; /* La valeur lue depusi l'entrée */
*erreur = 0;
 
size_t max = 10; /* Nombre d'éléments utilisables */
/* La boucle */
size_t i = 0; /* Nombre d'éléments utilisés */
while (lire_entree(&valeur))
int *ptr = malloc(max * sizeof(int)); /* Première allocation de mémoire */
if (ptr == NULL)
{
*erreur = 1;
}
else
{
intif valeur;(i >= max)
/* La boucle */
while (lire_entree(&valeur))
{
/* Il n'y a plus de place pour stocker 'valeur' dans 'ptr[i]' */
if (i >= max)
max = max + 10; break;
int *tmp = realloc(ptr, max * sizeof(int));
if (itmp >== maxNULL)
{
/* Il n'yrealloc a pluséchoué de: placeon poursort stockerde 'valeur'la dans 'ptr[i]'boucle */
max*erreur = max * 21;
int *tmp = realloc(ptr, max * sizeof(int))break;
if (tmp == NULL)}
{else
{
/* realloc a échoué : on sort de la boucle */
*erreurptr = 1tmp;
break;
}
else
{
ptr = tmp;
}
}
ptr[i] = valeur;
i++;
}
*erreurptr[i] = 1valeur;
}i++;
}
*taille = i;
return ptr;
}</pre>
Ici, on utilise <tt>max</tt> pour se souvenir du nombre d'éléments que contient la zone de mémoire allouée, et <tt>i</tt> pour le nombre d'éléments effectivement utilisés. Quand on a pu lire un entier depuis l'entrée, et que <tt>i</tt> vaut <tt>max</tt>, on sait qu'il n'y a plus de place disponible et qu'il faut augmenter la taille de la zone de mémoire. Ici, on doubleincrémente la taille <tt>max</tt> de 10 à chaque fois, mais il est aussi possible d'incrémenter de 10la élements,multiplier oupar de 1002, ou d'utiliser toute autre formule. On utilise par ailleurs le fait que, quand le pointeur envoyé à <tt>realloc</tt> est nul, la fonction se comporte comme <tt>malloc</tt>.

Le choix de cette formule résulte d'un compromis :
* augmenter <tt>max</tt> peu à peu permet de ne pas gaspiller trop de mémoire, mais on appellera <tt>realloc</tt> très souvent.
* augmenter très vite <tt>max</tt> génère relativement peu d'appels à <tt>realloc</tt>, mais une grande partie de la zone mémoire peut être perdue.