« Programmation C/Gestion de la mémoire » : différence entre les versions
Contenu supprimé Contenu ajouté
→<code>free</code>: libération de mémoire : précision |
balise source + corr mineures |
||
Ligne 24 :
=== <code>malloc</code>: allocation de mémoire ===
La fonction la plus simple d'allocation mémoire est <code>malloc</code> :
<source lang="c">
#include <stdlib.h>
void * malloc(size_t taille);
</
Elle prend en argument la ''taille'' que l'on veut allouer et renvoie un pointeur vers une zone mémoire allouée ou un pointeur nul si la demande échoue (la cause d'erreur la plus courante étant qu'il n'y a plus assez d'espace mémoire disponible). Trois remarques peuvent être faites :
#<code>malloc</code> renvoie une valeur de type <code>void *</code>, il n'est pas nécessaire de faire une conversion explicite (cela est nécessaire en C++) ;
Ligne 36 ⟶ 38 :
Par exemple, si on souhaite réserver une zone mémoire pour y allouer un entier:
<source lang="c">
{
}
{
}
</source>
Si on souhaite allouer de l'espace pour une structure de données plus complexe :
<source lang="c">
{
}
</source>
=== <code>free</code>: libération de mémoire ===
Le C ne possède pas de mécanisme de ramasse-miettes, la mémoire allouée dynamiquement par un programme doit donc être explicitement libérée. La fonction <tt>free</tt> permet de faire cette libération.
<source lang="c">
void free( void * pointeur );
</source>
La fonction prend en argument un pointeur vers une zone mémoire précédemment allouée par un appel à malloc, calloc ou realloc et libère la zone mémoire pointée.
Exemple :
<source lang="c">
/* Déclaration et initialisation */
int *ptr = NULL;
{
}
{
}
</source>
L'utilisation du pointeur après libération de la zone allouée (ou la double libération d'une même zone mémoire) est une erreur courante qui provoque des résultats imprévisibles. Il est donc conseillé :
Ligne 115 ⟶ 124 :
Syntaxe :
<source lang="c">
void * calloc(size_t nb_element, size_t taille);
</
De manière similaire à <tt>malloc</tt>, <tt>calloc</tt> retourne un pointeur de type <tt>void*</tt> pointant une zone de <tt>''nb_element''*''taille''</tt> octets allouée en mémoire, dont tous les octets seront initialisés à 0, ou retourne un pointeur nul en cas d'échec.
Exemple :
<source lang="c">
/* allouer un tableau de 5 entiers */
int* ptr = calloc ( 5, sizeof(int) );
</source>
Le pointeur contient l'adresse du premier élément du tableau :
<source lang="c">
*ptr = 3; /* premier entier : 3 */
</source>
Le pointeur peut être utilisé comme un tableau classique pour accéder aux éléments qu'il contient :
<source lang="c">
ptr[2] = 4; /* équivaut à *(ptr+2) = 4; */
</source>
=== <code>realloc</code> ===
Ligne 136 ⟶ 153 :
Syntaxe :
<source lang="c">
void * realloc(void * ancien_bloc, size_t nouvelle_taille);
</source>
<code>realloc</code> tentera de réajuster la taille du bloc pointé par ''ancien_bloc'' à la nouvelle taille spécifiée. À noter :
Ligne 147 ⟶ 167 :
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 :
<source lang="c">
/* Ce code contient une erreur volontaire. */
void f(void)
{
Ligne 155 ⟶ 177 :
/* ... */
}
</
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 :
<source lang="c">
#include <stdint.h> /* pour SIZE_MAX */
#include <stdlib.h>
Ligne 176 ⟶ 200 :
return tmp;
}
</
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.
Ligne 186 ⟶ 211 :
==== Gestion d'erreur ====
Pour gérer correctement le cas d'échec, on ne peut faire ainsi:
<source lang="c">
/* Ce code contient une erreur volontaire. */
int *ptr = malloc(10 * sizeof(int));
if (ptr != NULL)
Ligne 196 ⟶ 223 :
/* ... */
}
</
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é. Ce type d'erreur s'appelle une ''fuite mémoire''. Il faut donc faire ainsi:
<source lang="c">
int *ptr = malloc(10 * sizeof(int));
if (ptr != NULL)
Ligne 219 ⟶ 248 :
}
}
</
==== Exemple ====
Ligne 226 ⟶ 255 :
* 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 augmente la taille d'un tableau au fur et à mesure avec <tt>realloc</tt>.
<source lang="c">
/* 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).
Ligne 263 ⟶ 294 :
*taille = i;
return ptr;
}
</source>
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 incrémente la taille <tt>max</tt> de 10 à chaque fois, mais il est aussi possible de la multiplier par 2, 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>.
Ligne 276 ⟶ 309 :
Pour éviter des erreurs, un pointeur devrait '''toujours''' être initialisé à NULL lors de sa déclaration. Méditons sur l'exemple suivant:
<source lang="c">
</
Ce code va compiler! Si on est chanceux l'exécution de ce code provoquera une erreur à la troisième étape lorsqu'on déréférence <code>ptr</code>. Si on l'est moins, ce code s'exécute sans souci et stocke dans <code>var</code> une valeur aléatoire, ce qui pourrait provoquer (et encore...) une erreur lors d'une utilisation ultérieure de la variable <code>var</code> alors que l'erreur réelle se situe bien plus en amont dans le code! Une variable (automatique) de type pointeur est en effet comme toutes les autres variables: si elle n'est pas initialisée explicitement, son contenu est indéfini; son déréférencement peut donc causer n'importe quel comportement.
Ligne 293 ⟶ 326 :
La recopie d'un pointeur dans un autre n'alloue pas une nouvelle zone. La zone est alors référencée par deux pointeurs. Un problème peut survenir si on libère la zone allouée sans réinitialiser '''tous''' les pointeurs correspondants :
<source lang="c">
int* ptr2 = ptr; // ptr2 pointe la même zone mémoire
</source>
En règle générale, il faut éviter que plusieurs variables pointent la même zone mémoire allouée.
Ligne 311 ⟶ 346 :
La perte du pointeur associé à un secteur mémoire rend impossible la libération du secteur à l'aide de free. On qualifie cette situation de fuite mémoire car des secteurs demeurent réservés sans avoir été désalloués. Cette situation perdure jusqu'à la fin du programme principal. Voyons l'exemple suivant:
<source lang="c">
int* ptr = NULL; // un pointeur
while (i < 1001) {
ptr = malloc(sizeof(int)); // on écrase la valeur précédente de ptr par une nouvelle
if (ptr != NULL)
/* traitement ....*/
}
}
</source>
À la sortie de cette boucle on a alloué un millier d'entiers soit environ 2000 octets, que l'on ne peut pas libérer car le pointeur ptr est écrasé à chaque itération (sauf la dernière). La mémoire disponible est donc peu à peu grignotée jusqu'au dépassement de sa capacité. Ce genre d'erreurs est donc à proscrire pour des processus tournant en boucle pendant des heures, voire indéfiniment. Si son caractère cumulatif la rend difficile à détecter, il existe de nombreux utilitaires destinés à traquer la moindre fuite.
|