Allocation dynamique

Le nombre d'octets nécessaires pour stocker un type de données n'est pas fixé dans la norme du langage C. Le même programme aura donc un comportement différent sur des plateformes différentes. La plupart du temps ces différences n'ont pas d'effet visible, mais il arrive qu'elles soient la source d'erreurs.

Lorsque la taille en octets d'une donnée joue un rôle important, il est utile de pouvoir la calculer dans le code en ayant recours à un opérateur dédié : sizeof.

int main(void) {
  int n;
  double x;
  short t[14];
 
  printf("n : %zu octets.\n", sizeof n);
  printf("x : %zu octets.\n", sizeof x);
  printf("t : %zu octets.\n", sizeof t);
  return EXIT_SUCCESS;
}

Il est possible de connaître la taille d'un type de donnée sans forcément passer par une variable ou même une constante. On emploie alors une conversion «à vide» :

int main(void) {
  printf("long double : %zu octets.\n", sizeof(long double));
  return EXIT_SUCCESS;
}

Remarque Cette notation conduit de nombreuses personnes à croire que sizeof est une fonction. C'est la source de fautes graves car l'utilisation de sizeof est susceptible de rencontrer des problèmes de priorités entre opérateurs qu'une fonction n'aurait pas.

Jusqu'ici, notre seule méthode pour réserver de la mémoire a été de déclarer des variables et des tableaux. La mémoire ainsi rendue disponible fait partie d'une zone nommée pile, qui se situe dans la partie «haute» de la mémoire (les grandes adresses).

Cette technique présente deux caractéristiques parfois gênantes :

  • la mémoire n'est disponible que jusqu'à la fermeture du bloc contenant la déclaration,
  • la quantité de mémoire réservée doit être connue à la compilation.

Il existe une autre zone mémoire où nous pouvons réserver de la mémoire : le tas. Le mécanisme de l'allocation est différent et permet de s'affranchir des limitations énumérées ci-dessus.

int main(void) {
  int* p = NULL;
  p = (int*) malloc(2*sizeof(int));
  if (p) {
    printf("Entrez un entier : ");
    scanf("%d", p);
    printf("Entrez un second entier : ");
    scanf("%d", p + 1);
    printf("Leur somme vaut %d.\n", p[0] + p[1]);
    free(p);
    return EXIT_SUCCESS;
  } else {
    puts("Espace mémoire insuffisant !");
    return EXIT_FAILURE;
  }
}

Dans la bibliothèque standard (stdlib.h), la fonction malloc (contraction de memory allocation) cherche un espace mémoire de la taille en octets fournie en argument. Si un tel espace n'est pas disponible, elle renvoie NULL. Sinon, elle réserve cet espace et renvoie l'adresse de son premier octet.

La fonction free prend en argument l'adresse du premier octet d'un espace mémoire dynamiquement alloué et libère cet espace (pour qu'il puisse être réutilisé).

Remarque Il est très facile d'oublier de libérer la mémoire lorsqu'elle n'est plus utilisée. Peu à peu, la mémoire disponible diminue : on parle de fuite de mémoire. Veillez à acquérir les bons réflexes pour éviter de contribuer à propager cette maladie trop répandue.

Toujours dans la biliothèque standard (stdlib.h), la fonction calloc (contraction de cell allocation) est une variante de malloc qui prend deux arguments : le nombre de valeurs à stocker et la taille en octets d'une de ces valeurs. De plus (et c'est ce qui mérite qu'on en parle), la mémoire ainsi réservée voit chacun de ses bits mis à zéro.

int main(void) {
  double* p = (double*) calloc(5, sizeof(double));
  if (p) {
    int i;
    for(i = 0; i < 5; i++)
      printf("%.2f ", p[i]);
    putchar('\n');
    free(p);
    return EXIT_SUCCESS;
  } else {
    puts("Espace mémoire insuffisant !");
    return EXIT_FAILURE;
  }
}

Plus utile encore, la fonction realloc permet de «redimensionner» une zone mémoire. Elle alloue en fait une nouvelle zone, déplace les données et désalloue l'ancienne zone. Si la zone d'arrivée est plus petite, les données situées à la fin de la zone d'origine sont perdues. Si la zone d'arrivée est plus grande, les données d'origine sont suivies de données non initialisées. Si elle échoue, realloc ne déplace rien et renvoie NULL.

int main(void) {
  char* p = (char*) malloc(6*sizeof(char));
  if (p) {
    char* q = NULL;
    strcpy(p, "Hello");
    puts(p);
    q = (char*) realloc(p, 11*sizeof(char));
    if (q) {
      strcat(q, " Dave");
      puts(q);
      free(q);
      return EXIT_SUCCESS;
    } else {
      free(p);
      puts("Espace mémoire insuffisant !");
      return EXIT_FAILURE;
    }
  } else {
    puts("Espace mémoire insuffisant !");
    return EXIT_FAILURE;
  }
}

  1. Singletons. Écrivez un programme qui reçoit une liste de réels et affiche uniquement les valeurs présentes une seule fois dans la liste.

    Le programme demandera d'abord à l'utilisateur combien de réels il souhaite rentrer, puis il demandera un à un tous les réels. Il affichera ensuite, sur une seule ligne, les valeurs qui n'ont pas été répétées.

  2. Palindromes. Écrivez un programme qui teste si les arguments passés sur sa ligne de commande sont des palindromes. Vous vous appuierez sur la fonction suivante :

    char* inverse(const char* s);

    Cette fonction, que vous devrez définir, doit créer et renvoyer une chaîne contenant les mêmes caractères que s mais en ordre inverse. Exemple de session :

    bob@box:~$ ./a.out laval denis luc radar
    laval est un palindrome,
    denis n'est pas un palindrome,
    luc n'est pas un palindrome,
    radar est un palindrome.
    

    Remarque Choisissez soigneusement l'endroit où placer l'appel à free.

  3. Précognition. Reprenez le premier exercice et faites-le marcher sans demander au préalable le nombre de réels. L'utilisateur devra taper q pour quitter.

  4. Premiers. Écrivez un programme qui demande en boucle un nombre n strictement positif et affiche le n-ième nombre premier. Taper 0 permet de quitter le programme.

    Pour commencer, définissez une fonction qui détermine si un entier est premier ou pas.

    À l'aide de cette fonction, vous pourrez remplir un tableau avec tous les premiers nombres premiers en ordre croissant. Si le rang demandé est déjà connu, la réponse ne demande aucun calcul. Si le rang demandé n'a pas encore été obtenu, on remplit le tableau jusqu'à ce rang (si le rang demandé dépasse la capacité du tableau, il est redimensionné).

  5. Justification. Écrivez une fonction qui lit un mot à la console et le renvoie sous la forme d'une chaîne de caractères. Notez que cette fonction devra s'acquitter de sa tâche quelle que soit la longueur du mot. Si les premiers caractères lus sont des caractères blancs, ils seront ignorés et sautés.

    À l'aide de cette fonction, écrivez un programme qui lit à la console un paragraphe (terminé par un saut de ligne) et qui l'affiche ensuite en une colonne de texte justifié large de 50 caractères. Exemple de session :

    bob@box:~$ ./a.out
    Entrez votre texte : Lorem ipsum dolor sit amet, consectetur adipisici elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
    
    Lorem ipsum dolor sit amet, consectetur adipisici
    elit,  sed do eiusmod tempor incididunt ut labore
    et  dolore magna aliqua. Ut enim ad minim veniam,
    quis nostrud exercitation ullamco laboris nisi ut
    aliquip  ex ea commodo consequat. Duis aute irure
    dolor  in  reprehenderit  in voluptate velit esse
    cillum dolore eu fugiat nulla pariatur. Excepteur
    sint  occaecat  cupidatat  non  proident, sunt in
    culpa  qui  officia  deserunt  mollit anim id est
    laborum.
    

retour à la page d'accueil

retour au sommet