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

Contenu supprimé Contenu ajouté
ajout d'un exemple pour realloc
Ligne 118 :
 
=== <code>realloc</code> ===
La fonction <tt>realloc</tt> est utilisée pour changer (agrandir ou réduire) la taille de lad'une zone allouée par <tt>malloc</tt>, <tt>calloc</tt>, ou <tt>realloc</tt>.
 
Syntaxe :
''pointeur'' = realloc( ''pointeur'', ''nouvelle_taille'' );
 
* En cas de succès, <tt>realloc</tt> alloue un espace mémoire de taille ''nouvelle_taille'', copie le contenu pointé par le paramètre ''pointeur'' dans ce nouvel espace (en tronquant éventuellement si la nouvelle taille est inférieure à la précédente), puis libère l'espace pointé et retourne un pointeur vers la nouvelle zone mémoire.
Si la nouvelle taille spécifiée est supérieure à celle de la zone actuellement allouée, cette fonction peut retourner une adresse nulle si la quantité de mémoire disponible est insuffisante.
 
* En cas d'échec, cette fonction ne libère pas l'espace mémoire actuel, et retourne une adresse nulle.
 
Pour gérer correctement le cas d'échec, on ne peut faire ainsi:
<pre>/* Ce code contient une erreur volontaire. */
int *ptr = malloc(10 * sizeof(int));
if (ptr != NULL)
{
/* ... */
 
/* On se rend compte qu'on a besoin d'un peu plus de place. */
ptr = realloc(ptr, 20 * sizeof(int));
/* ... */
}
</pre>
En effet, si <tt>realloc</tt> échoue, la valeur de <tt>ptr</tt> est alors nulle, et on aura perdu la référence vers l'espace de taille <tt>10 * sizeof(int)</tt> qu'on a déjà alloué. Il faut donc faire ainsi:
<pre>
int *ptr = malloc(10 * sizeof(int));
if (ptr != NULL)
{
/* ... */
 
/* On se rend compte qu'on a besoin d'un peu plus de place. */
int *tmp = realloc(ptr, 20 * sizeof(int));
if (tmp == NULL)
{
/* Exemple de traitement d'erreur minimaliste */
free(ptr);
return EXIT_FAILURE;
}
else
{
ptr = tmp;
/* On continue */
 
}
}
</pre>
 
==== Exemple ====
<tt>realloc</tt> peut être utilisé quand on souhaite boucler sur une entrée dont la longueur peut être indéfinie, et qu'on veut gérer la mémoire assez finement. Dans l'exemple suivant, on suppose définie une fonction 'lire_entree' qui :
* 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 du tableau au fur et à mesure avec <tt>realloc</tt>.
<pre>/* Fonction qui utilise 'lire_entree' pour lire un nombre indéterminé
* d'entiers.
* Renvoie l'adresse d'un tableau d'entiers (NULL si aucun entier n'est lu).
* 'taille' est placé au nombre d'éléments lus (éventuellement 0).
* 'erreur' est placée à une valeur non nulle en cas d'erreur, à une valeur nulle sinon.
*/
int *traiter_entree(size_t *taille, int *erreur)
{
/* On initialise le traitement */
*erreur = 0;
size_t max = 10; /* Nombre d'éléments utilisables */
size_t i = 0; /* Nombre d'éléments utilisés */
int *ptr = malloc(max * sizeof(int)); /* Première allocation de mémoire */
if (ptr == NULL)
{
*erreur = 1;
}
else
{
int valeur;
/* La boucle */
while (lire_entree(&valeur))
{
if (i >= max)
{
/* Il n'y a plus de place pour stocker 'valeur' dans 'ptr[i]' */
max = max * 2;
int *tmp = realloc(ptr, max * sizeof(int));
if (tmp == NULL)
{
/* realloc a échoué : on sort de la boucle */
*erreur = 1;
break;
}
else
{
ptr = tmp;
}
}
ptr[i] = valeur;
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 double la taille <tt>max</tt> à chaque fois, mais il est aussi possible d'incrémenter de 10 élements, ou de 100, ou d'utiliser toute autre formule. 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.
Une allocation mémoire est une opération qui peut être coûteuse en terme de temps, et un grand nombre d'allocations mémoire peut fractionner l'espace mémoire disponible, ce qui alourdit la tâche de l'allocateur de mémoire, et au final peut causer des pertes de performance de l'application. Aucune formule n'est universelle, chaque situation doit être étudiée en fonction de différents paramètres (système d'exploitation, capacité mémoire, vitesse du matériel, taille habituelle/maximale de l'entrée...).
Toutefois, l'essentiel est bien souvent d'avoir un algorithme qui marche, l'optimisation étant une question secondaire. Dans une telle situation, utilisez d'abord une méthode simple, et n'en changez que si le comportement du programme devient gênant.
 
== Problèmes et erreurs classiques ==