Le langage C - allocation dynamique

Tableaux vus comme pointeurs

On a vu qu'un tableau est une série de variables d'un type donné. Cette série est stockée en mémoire en un seul bloc. En particulier, on peut le représenter entièrement par un pointeur sur sa première valeur. Il est en effet facile de retrouver l'adresse de l'élément numéro i si on connaît l'adresse de l'élément zéro: il suffit d'ajouter i fois la taille d'un élément à cette adresse. L'utilisation de pointeurs comme dans l'exemple suivant,

float x[25] ;   /*  tableau de 25 réels     */
float *adr_x ;  /*  pointeur vers un float  */

adr_x = &x[0] ; /*  faire pointer adr_x vers le premier élément de x  */

x[0] = 5 ;      /*  ces deux instructions modifient la  */
*adr_x = 5 ;    /*  valeur du premier élément de x      */

x[10] = 5 ;     /*  ces deux instructions modifient la  */
*(adr_x + 10) = 5 ;/*  valeur du 10ème élément de x     */

montre que l'écriture façon tableau et l'écriture en utilisant un pointeur sont équivalentes. D'ailleurs, lorsqu'on utilise x seul (sans les crochets), par exemple dans un appel de fonction, il est converti automatiquement en le pointeur &x[0]. La troisième ligne ci-dessus aurait pu s'écrire adr_x = x, et par conséquent x[10] = 5; est équivalent à *(x+10) = 5;.

Cette équivalence à deux intérêts:

  1. Lorsqu'on passe un tableau comme argument d'une fonction, simplement le pointeur vers son premier élément est copié. C'est beacoup moins couteux en mémoire et temps qu'une copie du tableau entier.
  2. Il arrive souvent qu'on ne sache qu'à l'execution du programme quelle taille un tableau fera. On ne peut plus utiliser de tableau (sa taille doit être connue au moment de la compilation), mais on peut utiliser un pointeur et l'allocation dynamique à la place.

Allocation dynamique d'un tableau

Il y a deux moyens de créer un tableau de taille non connue à priori: Déclaration d'un tableau de taille inconnue: float x[] ; ou déclaration d'un pointeur: float *x ;. Les deux s'utilisent de la même manière par la suite.

Il est alors nécessaire d'utiliser l'allocation dynamique de mémoire. Cela consiste à réserver de la place dans la mémoire au moment de l'exécution. Cette opération s'effectue à l'aide des fonctions malloc et free, définies dans la librairie standard (il faut donc inclure le fichier #include <stdlib.h> au début du programme), qui nécessite l'utilisation des pointeurs.

Allocation de mémoire

La fonction d'allocation dynamique de mémoire se nomme malloc. Elle a pour déclaration (dans stdlib.h):

void *malloc (size_t size) ;

La fonction malloc a pour argument la taille (en octets) du bloc que l'on souhaite réserver en mémoire, et renvoie un pointeur sur le début du bloc. L'argument est du type size_t, qui est en fait un entier. Il faut ici faire attention au fait que le nombre d'octets demandé n'est pas, en général, égal au nombre d'éléments du tableau que l'on veut créer. En effet, nous avons vu que chaque type de variable occupe une taille en mémoire différente. Cette taille peut, de plus, dépendre de la machine utilisée, c'est pourquoi il faut utiliser la fonction sizeof pour la détéminer. Par exemple la quantité de mémoire nécessaire à stoquer n variables d'un type donné se calcule de la façon suivante:

n*sizeof(char)   pour des caractères
n*sizeof(int)    pour des entiers
n*sizeof(float)  pour des réels simples précision
etc...

La fonction renvoit un pointeur de type non défini (void *), qu'il faut convertir en pointeur du type de la variable demandée. Ainsi pour allouer un tableau de n variables d'un type donné, l'appel de malloc se fera de la façon suivante:

int n ;          /*   taille du tableau    */
float *fptr ;    /*   pointeur reel simple precision    */
int *iptr ;      /*   pointeur entier    */
char *cptr ;     /*   pointeur caractere    */
double *dptr ;   /*   pointeur reel double precision    */
n = 100 ;        /*   100 variables dans le tableau    */
/*    allocation dynamique   */
fptr = (float *)malloc (n * sizeof (float)) ;
iptr = (int *)malloc (n * sizeof (int)) ;
cptr = (char *)malloc (n * sizeof (char)) ;
dptr = (double *)malloc (n * sizeof (double)) ;

Une fois la fonction malloc appelée, il est impératif de vérifier que le système a trouvé la quantité de mémoire souhaitée (la mémoire n'est pas infinie). Lorsque la tentative d'allocation échoue, la fonction malloc renvoit un pointeur NULL. Il faut donc tester si le pointeur retourné est égal à NULL; si c'est le cas, on ne peut pas et on ne doit pas l'utiliser:

int n ;          /*   taille du tableau    */
float *fptr ;    /*   pointeur réel simple précision    */
n = 100 ;        /*   100 variables dans le tableau    */
/*    allocation dynamique   */
fptr = (float *)malloc (n * sizeof (float)) ;
/*    test du résultat       */
if (fptr == NULL)
{        /*   l'allocation a echoué:    */
/*    message d'erreur et arrêt du programme    */
    printf ("l'allocation a echoue\n") ;
    exit (1) ;
}

Utilisation des tableaux alloués

Les tableaux alloués de cette façon s'utilisent exactement comme les tableaux standards.

Libération de la mémoire

Lorsqu'on n'a plus besoin d'un tableau alloué dynamiquement, il est impératif de libérer la mémoire. Cette opération se fait avec la fonction free (fichier en-tête stdlib.h). Sa déclaration est:

void free (void *ptr) ;

son seul argument est le pointeur qui a été retourné par malloc. Le bloc de mémoire vers lequel il pointe est ainsi libéré par la fonction free. Cette fonction n'a pas de valeur de retour (void). Reprenons notre exemple:

int n ;          /*   taille du tableau    */
int i ;          /*   un indice            */
float *fptr ;    /*   pointeur reel simple precision    */
n = 100 ;        /*   100 variables dans le tableau    */
/*    allocation dynamique   */
fptr = (float *)malloc (n * sizeof (float)) ;
/*    test du resultat       */
if (fptr == NULL)
{ /*  l'allocation a echoué: message d'erreur et arrêt du programme */
    perror ("echec de l'allocation") ;
    exit (0) ;
}
/*   on peut utiliser le tableau...                                 */
/*   (l'exécution de ce qui suit n'a lieu que si (fptr != NULL))    */
/*   exemple: on affecte des valeurs aux éléments du tableau        */
for (i=0 ; i<n ; i++)    /*  boucle de 0 à n-1 (n elements)         */
{
    fptr[i] = (float)i ; /*  chaque element a la valeur de l'indice */
}                        /*  noter la conversion en réel            */

/*    ...                                                           */

/*   Quand on n'a plus besoin du tableau, on libere la memoire      */
free (fptr) ;

Chaînes de caractères

Les chaînes de caractères sont le principal exemple d'utilisation des pointeurs. En effet, le C n'a pas, contrairement à d'autres langages, de type spécifique pour manipuler les chaînes de caractères. Leur représentation se fait par un tableau de caractères (type char) terminé par le caractère nul, qui en C signifie la fin d'une chaîne de caractères. On les manipule ensuite à l'aide d'un pointeur sur le premier caractère de la chaîne, ce qui permet d'accéder à la chaîne en entier. Ainsi, toutes les fonctions C servant à panipuler les chaînes de caractères utilisent comme arguments des pointeurs sur des variables de type char ces fonctions sont définies dans le fichier en-tête string.h. Nous n'entrerons pas dans les détails ici, pour un exemple, voir charex.c

Arithmétique sur les pointeurs

Il est possible de faire certaines opérations sur des pointeurs, mais leur interprétation est différente des opérations sur des variables, du fait de leur nature. En effet, un pointeur représente l'adresse mémoire d'une variable d'un type donné (donc stocké en mémoire sur un nombre donné d'octets). De ce fait, augmenter de 1 un pointeur revient à le faire pointer sur l'élement suivant dudit type, ce qui est différent d'augmenter l'adresse mémoire de 1 octet. Ce type de manipulation n'est utilisée que dans des cas où la rapidité d'exécution est essentielle (c'est difficilement lisible) vous en trouverez un exemple à la fin du programme charex.c

Auteur(s) : Anciens, A. Daerr. Dernière modification : Tue Nov 21 02:10:49 2006. [validate XHTML]