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

Contenu supprimé Contenu ajouté
m Formatage, ajout de code
DannyS712 (discussion | contributions)
m <source> -> <syntaxhighlight> (phab:T237267)
Ligne 28 :
La fonction la plus simple d'allocation mémoire est <code>malloc</code> :
 
<sourcesyntaxhighlight lang="c">
#include <stdlib.h>
 
void * malloc(size_t taille);
</syntaxhighlight>
</source>
 
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 :
Ligne 41 :
Par exemple, si on souhaite réserver une zone mémoire pour y allouer un entier :
 
<sourcesyntaxhighlight lang="c">
/* Déclaration et initialisation */
int *ptr = NULL;
Ligne 58 :
/* décider du traitement en cas d'erreur */
}
</syntaxhighlight>
</source>
 
Si on souhaite allouer de l'espace pour une structure de données plus complexe :
 
<sourcesyntaxhighlight lang="c">
typedef struct{
double b;
Ligne 82 :
ptr->suivant = NULL;
}
</syntaxhighlight>
</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 <code>free</code> permet de faire cette libération.
 
<sourcesyntaxhighlight lang="c">
void free( void * pointeur );
</syntaxhighlight>
</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.
Ligne 95 :
Exemple :
 
<sourcesyntaxhighlight lang="c">
/* Déclaration et initialisation */
int *ptr = NULL;
Ligne 115 :
/* décider du traitement en cas d'erreur */
}
</syntaxhighlight>
</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 128 :
Syntaxe :
 
<sourcesyntaxhighlight lang="c">
void * calloc(size_t nb_element, size_t taille);
</syntaxhighlight>
</source>
 
De manière similaire à <code>malloc</code>, <code>calloc</code> retourne un pointeur de type <code>void*</code> pointant une zone de <code>''nb_element''*''taille''</code> octets allouée en mémoire, dont tous les bits seront initialisés à 0, ou retourne un pointeur nul en cas d'échec.
Ligne 136 :
Exemple :
 
<sourcesyntaxhighlight lang="c">
/* allouer un tableau de 5 entiers */
int* ptr = calloc ( 5, sizeof(int) );
</syntaxhighlight>
</source>
 
Le pointeur contient l'adresse du premier élément du tableau :
<sourcesyntaxhighlight lang="c">
*ptr = 3; /* premier entier : 3 */
</syntaxhighlight>
</source>
Le pointeur peut être utilisé comme un tableau classique pour accéder aux éléments qu'il contient :
<sourcesyntaxhighlight lang="c">
ptr[0] = 3; /* équivaut à *ptr = 3; */
ptr[1] = 1; /* équivaut à *(ptr+1) = 1; */
ptr[2] = 4; /* équivaut à *(ptr+2) = 4; */
</syntaxhighlight>
</source>
 
Notez que <code>calloc</code> place tous les ''bits'' à zéro, mais que ce n'est pas nécessairement une représentation valide pour un pointeur nul ni pour le nombre zéro en représentation flottante. Ainsi, pour initialiser à zéro un tableau de <code>double</code> de manière portable, par exemple, il est nécessaire d'assigner la valeur <code>0.0</code> à chaque élément du tableau. Étant donné qu'on initialise chaque élément « manuellement », on peut dans ce cas utiliser <code>malloc</code> plutôt que <code>calloc</code> (la première étant normalement beaucoup plus rapide que la seconde).
Ligne 159 :
Syntaxe :
 
<sourcesyntaxhighlight lang="c">
void * realloc(void * ancien_bloc, size_t nouvelle_taille);
</syntaxhighlight>
</source>
 
<code>realloc</code> tentera de réajuster la taille du bloc pointé par ''ancien_bloc'' à la nouvelle taille spécifiée. À noter :
Ligne 174 :
 
{{Erreur volontaire}}
<sourcesyntaxhighlight lang="c">
void f(void)
{
Ligne 182 :
/* ... */
}
</syntaxhighlight>
</source>
 
Lorsque <code>realloc</code> reçoit la valeur de <code>tab</code>, qui est un pointeur sur le premier élément (i.e. <code>&tab[0]</code>), 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 :
 
<sourcesyntaxhighlight lang="c">
#include <stdint.h> /* pour SIZE_MAX */
#include <stdlib.h>
Ligne 205 :
return tmp;
}
</syntaxhighlight>
</source>
 
La fonction <code>double</code> en elle-même ne comporte pas d'erreur, mais elle peut causer des plantages suivant la valeur de <code>ptr</code> 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 <code>ptr</code>... et que les programmeurs qui l'utilisent y fassent attention.
Ligne 218 :
 
{{Erreur volontaire}}
<sourcesyntaxhighlight lang="c">
int *ptr = malloc(10 * sizeof(int));
if (ptr != NULL)
Ligne 228 :
/* ... */
}
</syntaxhighlight>
</source>
 
En effet, si <code>realloc</code> échoue, la valeur de <code>ptr</code> est alors nulle, et on aura perdu la référence vers l'espace de taille <code>10 * sizeof(int)</code> qu'on a déjà alloué. Ce type d'erreur s'appelle une ''fuite mémoire''. Il faut donc faire ainsi :
 
<sourcesyntaxhighlight lang="c">
int *ptr = malloc(10 * sizeof(int));
if (ptr != NULL)
Ligne 253 :
}
}
</syntaxhighlight>
</source>
 
==== Exemple ====
Ligne 261 :
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 <code>realloc</code>.
 
<sourcesyntaxhighlight lang="c">
/* Fonction qui utilise 'lire_entree' pour lire un nombre indéterminé
* d'entiers.
Ligne 300 :
return ptr;
}
</syntaxhighlight>
</source>
 
Ici, on utilise <code>max</code> pour se souvenir du nombre d'éléments que contient la zone de mémoire allouée, et <code>i</code> pour le nombre d'éléments effectivement utilisés. Quand on a pu lire un entier depuis l'entrée, et que <code>i</code> vaut <code>max</code>, 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 <code>max</code> 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é à <code>realloc</code> est nul, la fonction se comporte comme <code>malloc</code>.
Ligne 315 :
 
{{Erreur volontaire}}
<sourcesyntaxhighlight lang="c">
/* Déclaration sans initialisation */
int *ptr;
Ligne 322 :
/* On stocke dans var la valeur de la zone mémoire pointée par ptr*/
var = *ptr;
</syntaxhighlight>
</source>
 
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 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 332 :
 
{{Erreur volontaire}}
<sourcesyntaxhighlight lang="c">
int* ptr = malloc(sizeof(int)); // allouer une zone mémoire pour un entier
int* ptr2 = ptr; // ptr2 pointe la même zone mémoire
Ligne 344 :
*ptr2 = 10; /* <- résultat imprévisible (plantage de l'application, ...) */
</syntaxhighlight>
</source>
 
En règle générale, il faut éviter que plusieurs variables pointent la même zone mémoire allouée.
Ligne 355 :
 
{{Erreur volontaire}}
<sourcesyntaxhighlight lang="c">
int i = 0; // un compteur
int* ptr = NULL; // un pointeur
Ligne 365 :
}
}
</syntaxhighlight>
</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.