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

Contenu supprimé Contenu ajouté
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> :
 
<pre>
<source lang="c">
#include <stdlib.h>
 
void * malloc(size_t taille);
</presource>
 
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">
<pre>
/* Déclaration et initialisation */
int *ptr = NULL;
 
/* Allocation */
ptr = malloc(sizeof(int));
 
/* On vérifie que l'allocation a réussi. */
if (ptr != NULL)
{
{
/* Stockage de la valeur "10" dans la zone mémoire pointée par ptr */
*ptr = 10;
}
}
else
{
{
/* décider du traitement en cas d'erreur */
}
}</pre>
</source>
 
Si on souhaite allouer de l'espace pour une structure de données plus complexe :
 
<source lang="c">
<pre>
typedef struct{
int a;
double b;
MaStructure *suivant;
} MaStructure;
 
/* Déclaration et initialisation */
int *ptr = NULL;
 
/* Allocation */
ptr = malloc(sizeof(MaStructure));
 
if (ptr != NULL)
{
{
/* Initialisation de la structure nouvellement créée */
ptr->a = 10;
ptr->b = 3.1415;
ptr->suivant = NULL;
}
}</pre>
</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 );
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 :
 
/* Déclaration et initialisation */
<source lang="c">
int *ptr = NULL;
/* Déclaration et initialisation */
int *ptr = NULL;
/* Allocation */
ptr = malloc(sizeof(int));
 
/* On vérifie que l'allocation a réussi. */
if (ptr != NULL)
{
{
/* ... utilisation de la zone allouée ... */
/* Libérer la mémoire utilisée */
free(ptr);
ptr=NULL; /* Pour éviter les erreurs */
}
}
else
{
{
/* décider du traitement en cas d'erreur */
}
}
</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 :
 
<pre>
<source lang="c">
void * calloc(size_t nb_element, size_t taille);
</presource>
 
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 :
 
/* allouer un tableau de 5 entiers */
<source lang="c">
int* ptr = calloc ( 5, sizeof(int) );
/* 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 */
*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[0] = 3; /* équivaut à *ptr = 3; */
ptr[10] = 13; /* équivaut à *(ptr+1) = 13; */
ptr[21] = 41; /* équivaut à *(ptr+21) = 41; */
ptr[2] = 4; /* équivaut à *(ptr+2) = 4; */
</source>
 
=== <code>realloc</code> ===
Ligne 136 ⟶ 153 :
 
Syntaxe :
 
void * realloc(void * ancien_bloc, size_t nouvelle_taille);
<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 :
 
<pre>/* Ce code contient une erreur volontaire. */
<source lang="c">
/* Ce code contient une erreur volontaire. */
void f(void)
{
Ligne 155 ⟶ 177 :
/* ... */
}
</presource>
 
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>
<source lang="c">
#include <stdint.h> /* pour SIZE_MAX */
#include <stdlib.h>
Ligne 176 ⟶ 200 :
return tmp;
}
</presource>
 
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:
 
<pre>/* Ce code contient une erreur volontaire. */
<source lang="c">
/* Ce code contient une erreur volontaire. */
int *ptr = malloc(10 * sizeof(int));
if (ptr != NULL)
Ligne 196 ⟶ 223 :
/* ... */
}
</presource>
 
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:
 
<pre>
<source lang="c">
int *ptr = malloc(10 * sizeof(int));
if (ptr != NULL)
Ligne 219 ⟶ 248 :
}
}
</presource>
 
==== 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>.
 
<pre>/* Fonction qui utilise 'lire_entree' pour lire un nombre indéterminé
<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;
}
}</pre>
</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">
<pre>
/* ATTENTION: Ce code contient volontairement une erreur!!! */
/* Déclaration sans initialisation */
int *ptr;
int var;
 
/* On stocke dans b la valeur de la zone mémoire pointée par ptr*/
var = *ptr;
</presource>
 
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* ptr = malloc(sizeof(int)); // allouer une zone mémoire pour un entier
int* ptr2ptr = ptrmalloc(sizeof(int)); // ptr2allouer pointe la mêmeune zone mémoire pour un entier
int* ptr2 = ptr; // ptr2 pointe la même zone mémoire
*ptr = 5;
printf("%d\n", *ptr2 ); // affiche 5
/* libération */
free( ptr );
ptr = NULL;
*ptr2 = 10; /* <- résultat imprévisible (plantage de l'application, ...) */
</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 i=0; // un compteur
int* ptri =NULL 0; // un pointeurcompteur
int* ptr = NULL; // un pointeur
while(i<1001) {
while (i < 1001) {
ptr=malloc(sizeof(int)); // on écrase la valeur précédente de ptr par une nouvelle
ptr = malloc(sizeof(int)); // on écrase la valeur précédente de ptr par une nouvelle
if(ptr!=NULL) {
if (ptr != NULL)
/* traitement ....*/
} {
/* 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.