Allocation dynamique de la mémoire

Il s’agit de la motivation principale d’utilisation des pointeurs, dans tous les langages de programmation. Dans de très nombreux programmes en effet, on ne sait pas d’avance quelle place une donnée (un tableau par exemple) va devoir utiliser. Imaginons par exemple un traitement de texte : il est impossible de savoir quelle taille allouer au tableau de caractères qui contient le texte, puisque cette taille dépend de ce qui sera tapé par l’utilisateur.

La solution de facilité consiste à allouer une place fixe déterminée arbitrairement. Mais cela pénalise l’utilisateur, et peut poser des problèmes si la taille allouée est trop petite (pas assez de place) ou trop grande (encombrement de la mémoire). En outre, il faut savoir qu’il existe trois blocs importants de mémoire disponibles : le segment de données, le tas (anglais heap), qui n’est généralement limité que par la mémoire disponible pour le programme et enfin la pile (anglais stack), réservée aux arguments de fonctions.

La mémoire située dans le tas ne peut être allouée que dynamiquement, c’est-à-dire au moment de l’exécution du programme. La mémoire du segment de données est allouée à la compilation, en conséquence d’instructions comme celles-ci :

int i = 0;                      // quatre octets alloués
char *s = "Bonjour à tous.";    // 16 octets alloués
double matrice[3][3];           // 9*8 == 72 octets alloués
char tampon[256];               // 256 octets alloués

L’allocation dynamique peut se faire de deux façons différentes. La première consiste à employer des fonctions spéciales définies dans <alloc.h>. La plus connue de ces fonctions est malloc, qui réserve un bloc de mémoire de taille spécifiée, suivant le format :

void *malloc(size_t taille);

taille est le nombre d’octets souhaités (size_t est équivalent à unsigned). La fonction renvoie un pointeur qui vaut NULL (c’est-à-dire 0) s’il n’y a plus de place ou si taille == 0, et sinon sur le bloc alloué. On écrira ainsi, pour dupliquer une chaîne de caractères par exemple (ne pas oublier la place pour le zéro final) :

char *s1 = "Bonjour à tous!\n";
char *s2 = (char*) malloc( 1+strlen(s1) );
if (s2)  strcpy(s2, s1);

(en fait, il existe une fonction nommée strdup dans <string.h> qui fait ce travail). Notons d’autre part que le bloc alloué n’est pas remis à zéro. Il existe une fonction nommée memset pour cela.

Il existe une fonction cousine de malloc nommée calloc ; elle admet deux paramètres entiers, les multiplie et appelle malloc. On utilise souvent cette fonction pour des tableaux :

int dim = 10;
double *table = (double*) calloc(dim, sizeof(double));

Il arrive parfois que l’on souhaite modifier la taille d’un bloc alloué. On utilise alors realloc :

void *realloc(void *bloc, size_t taille);

taille est la nouvelle taille demandée, bloc est le bloc mémoire à réallouer. La fonction renvoie NULL si taille == 0 ou s’il n’y a pas assez de place en mémoire, et sinon un pointeur sur la nouvelle position de bloc. Par exemple, on peut augmenter la taille de notre bloc table, si dim vient à augmenter en cours de programme :

dim += 10;                 // dim augmente
table = (double*) realloc(table, dim*sizeof(double) );
if (!table) erreur("Plus de mémoire");

On doit ici faire la multiplication, car il n’y a pas d’équivalent à calloc pour la réallocation. On notera que, si le résultat doit être converti en double*, il n’est pas nécessaire de convertir table en void*, comme on l’a expliqué à propos de ce type spécial de pointeur.

Précisons que la fonction erreur doit arrêter le programme, car table est perdu si la réallocation a échoué, puisqu’on n’a plus le pointeur table d’origine.

Lorsqu’on a fini d’utiliser un bloc de mémoire, il est préférable de le libérer, afin qu’il puisse être réutilisé ultérieurement. On utilise pour cela la fonction free :

void free(void *bloc);

Il suffit donc d’écrire :

free(table);  table = NULL;

On a remis le pointeur à zéro, afin d’éviter des erreurs dans la suite. Il ne faut plus en effet utiliser un bloc libéré, de même qu’il ne faut pas utiliser un bloc incomplet : les erreurs d’écriture en mémoire sont particulièrement graves.

Notons que free ne doit être appliquée qu’à des blocs alloués par malloc, calloc ou realloc, sous peine d’erreurs très graves. Normalement, l’appel de free sur une valeur nulle est sans effet.

Exercice 3.8

Écrire une fonction qui ajoute deux chaînes l’une à l’autre. Il faudra allouer la mémoire nécessaire au résultat. On pourra utiliser les fonctions strlen et strcpy (longueur et copie de chaînes) de <string.h>.

Voir solution
Précédent Précédent Sommaire Sommaire Suivant Suivant