Programmation C
Gestion de la mémoire
Cours Exercices Correction des exercices

Organisation Allocation dynamique Désallocation

Organisation

  • Nécessité d'utiliser de la mémoire au cours du fonctionnement d'une application
    • Pour le stockage des instructions exécutées
      • Segment de texte (non traité ici)
    • Pour le stockage des informations gérées/stockées
      • Segments de données (variables globales et variables statiques, initialisées ou non)
      • Pile
      • Tas
  • Pile (stack)
    • Zone mémoire dédiée au stockage de toutes les variables "locales" existant au cours de la vie du programme
      • Paramètres des fonctions
        • Lors de tout appel de fonction, création d'une copie locale de chaque paramètre effectif endossée par le paramètre formel correspondant
        • Allocation sur la pile de la mémoire nécessaire au stockage de ces copies locales
        • Existence de ces copies locales durant l'exécution de la fonction
          -> Dépilement et cessation d'existence de ces copies à la fin de la fonction
      • Valeurs retour des fonctions
        • Empilement de la valeur retour au cours de son retour
      • Variables locales aux fonctions
        • Lors de toute exécution d'une fonction, allocation sur la pile de la mémoire nécessaire au stockage des variables de portée locale à la fonction
        • Dépilement à la fin de l'exécution de la fonction
          -> Cessation d'existence de ces variables
      • Variables locales aux blocs d'instructions
        • Lors de tout exécution d'un bloc d'instructions (séquence d'instructions délimitée par un couple accolade ouvrante - accolade fermante), allocation sur la pile de la mémoire nécessaire au stockage des variables de portée locale au bloc d'instructions
        • Dépilement à la fin de l'exécution du bloc
          -> Cessation d'existence de ces variables
    • Conclusion : empilement de toutes les variables locales dans la pile au cours de leur existence, dépilement à la fin de leur existence
    • Gestion automatique de ces empilements/dépilements au cours de la vie du programme
      -> Pas de la responsabilité du développeur de le faire
    • Problème pratique : taille de la pile
      • Pile énormément utilisée -> utilisation aussi rapide que possible nécessaire
      • Impossible d'agrandir ou de réduire la taille de la pile à chaque accès pour un empilement ou un dépilement
      • Agrandissement possible et géré automatiquement (définition possible de la taille initiale à la compilation et/ou à l'exécution), mais pas de réduction
      • Agrandissement limité par l'espace mémoire dont dispose l'application
      • Si plus de place, plantage
      • Se méfier de la quantité de mémoire nécessaire au stockage des paramètres de fonction et des variables locales de fonction (minimiser la place nécessaire quitte à sacrifier un peu de performance ?), surtout si on utilise la récursivité et que celle-ci est de profondeur importante
      • Attention : taille de la pile relativement limitée par défaut
  • Tas (heap)
    • Zone mémoire dédiée aux allocations dynamiques de mémoire (voir + loin)
    • Taille du tas : toute la mémoire allouée à l'application après retrait du segment de texte et des segments de données
      -> "Grande" taille

Allocation dynamique

  • Définition : réservation temporaire exclusive d'une certaine quantité de mémoire en un bloc d'octets contigus
  • Lors d'une allocation, quantité réservée déterminée au choix du développeur, seulement limitée par la possibilité de trouver une zone mémoire de taille suffisante
  • Possibilité de faire autant d'allocations mémoires différentes simultanées que souhaité dans la limite de la mémoire disponible
  • Fonctionnalité très importante des langages informatiques
    • Possibilité d'implanter des structures de données dynamiques de stockage d'information
      • liste
      • vecteur
      • ensemble
      • collection
      • pile
      • file
      • table de hachage
      • arbre
      • graphes
      • ...
    • Possibilité de développer des programmes qui vont pouvoir s'adapter à la taille des données qu'ils doivent traiter
    • Quantité de mémoire utilisée ajustée strictement à ce qui est nécessaire (pas de surutilisation)
    • ...
  • Allocation dynamique utilisée pour réserver des tableaux mais aussi des enregistrements uniques
  • Trois fonctions déclarées dans l'API standard stdlib.h (à inclure obligatoirement)
    • malloc
    • calloc
    • realloc
  • Fonctions utilisées en association avec l'opérateur sizeof car il est nécessaire de calculer en octets les tailles d'allocation
  • malloc
    • Allocation dynamique d'une quantité donnée de mémoire comptée en octets
    • Pas d'initialisation de la zone mémoire allouée
    • Syntaxe
      void* malloc(size_t nombreOctets);
      Allocation dynamique de nombreOctets octets contigus trouvés dans le tas
      Retour de l'adresse du premier octet de la zone d'octets allouée, NULL si échec
    • Exemples
    • #include <stdio.h>
      #include <stdlib.h>

      void main(void) {
        int* ptr = (int*) malloc(sizeof(int));
        if (ptr == NULL) {
          printf("Allocation avortee\n");
        }
        else {
          printf("Allocation reussie a l'adresse : %p\n", ptr);
          printf("%d\n", *ptr);
          *ptr = 111;
          printf("%d\n", *ptr);
        }
      }

      Malloc1.c - Malloc1.exe
      • Définition d'une variable de type int* (pointeur sur int) initialisée avec le résultat d'un appel à malloc pour une taille égale au nombre d'octets nécessaire au stockage d'un int
      • Nécessité de caster le résultat du malloc de type void* vers int* pour que l'affectation soit possible
      • Deux possibilités
        • Allocation non réussie, retour de NULL par la fonction malloc
        • Allocation réussie, retour de l'adresse du premier octet de la zone mémoire allouée
      • Si allocation réussie, affichage de l'adresse de la zone allouée, puis affichage du contenu (aléatoire) de cette zone mémoire comme s'il s'agissait d'un int, puis affectation du contenu avec la valeur 111 et nouvel affichage
      Allocation reussie a l'adresse : 000002391110BC90 286295408
      111

      #include <stdio.h>
      #include <stdlib.h>

      void main(void) {
        size_t taille1 = 5;
        printf("Allocation de %zu octets ", taille1 * sizeof(double));
        printf("pour un tableau de %zu double\n", taille1);
        double* ptr1 = (double*) malloc(taille1 * sizeof(double));
        if (ptr1 == NULL) {
          printf("Allocation avortee\n");
        }
        else {
          printf("Allocation reussie a l'adresse : %p\n", ptr1);
          printf("\n");
          for (int i = 0; i < taille1; i++) {
            printf("%lf\n", ptr1[i]);
          }
          printf("\n");
          for (int i = 0; i < taille1; i++) {
            ptr1[i] = 10.0;
          }
          for (int i = 0; i < taille1; i++) {
            printf("%lf\n", ptr1[i]);
          }
          printf("\n");
        }
        size_t taille2 = 80 * (1024ULL * 1024ULL * 1024ULL);
        double* ptr2 = (double*) malloc(taille2);
        printf("Tentative d'allocation de %zu octets ", taille2);
        printf("soit %zu Go\n", taille2 >> 30);
        if (ptr2 == NULL) {
          printf("Allocation avortee\n");
        }
        else {
          printf("Allocation reussie a l'adresse : %p\n", ptr2);
        }
      }

      Malloc2.c - Malloc2.exe
      • Définition d'une variable de type double* (pointeur sur double) initialisée avec le résultat d'un appel à malloc pour une taille égale au nombre d'octets nécessaire au stockage de cinq double
      • Nécessité de caster le résultat du malloc de type void* vers double* pour que l'affectation soit possible
      • Utilisation de la variable de type double* en tant que tableau de double
      • Si allocation réussie, affichage des valeurs (aléatoires) des éléments du tableau, puis affectation de ces éléments avec la valeur 10, puis enfin nouvel affichage
      • Calcul du nombre d'octets représenté par 80 Go
      • Tentative d'allocation pour cette quantité d'octets
      • Sauf à avoir beaucoup de mémoire dans l'ordinateur, tentative avortée
      Allocation de 40 octets pour un tableau de 5 double
      Allocation reussie a l'adresse : 0000020BFD2D0060

      0.000000
      0.000000
      0.000000
      0.000000
      0.000000

      10.000000
      10.000000
      10.000000
      10.000000
      10.000000

      Tentative d'allocation de 85899345920 octets soit 80 Go
      Allocation avortee
  • calloc
    • Allocation dynamique pour une quantité de mémoire permettant de stocker un nombre donné d'enregistrements occupant individuellement un nombre donné d'octets
    • Initialisation intégrale de la zone mémoire allouée avec des octets égaux à 0x00 (0 pour les entiers, 0.0 pour les réels, faux pour les booléens, code ASCII 0 pour les caractères, adresse NULL pour les pointeurs)
    • Syntaxe
      void* calloc(size_t nombreElements, size_t nombreOctetsParElement);
      Allocation dynamique de nombreElements*nombreOctetsParElement octets contigus trouvés dans le tas
      Retour de l'adresse du premier octet de la zone d'octets allouée, NULL si échec
    • Exemple
    • #include <stdio.h>
      #include <stdlib.h>

      void main(void) {
        size_t taille = 500000;
        printf("Allocation de %zu int\n",taille);
        int* ptr = (int*) calloc(taille, sizeof(int));
        if (ptr == NULL) {
          printf("Allocation avortee\n");
        }
        else {
          printf("Allocation reussie a l'adresse : %p\n", ptr);
          printf("\n");
          int cpt = 0;
          for (int i = 0; i < taille; i++) {
            if (ptr[i] == 0) {
              cpt++;
            }
          }
          printf("%d valeurs egales a 0 pour %zu attendu\n", cpt, taille);
        }
      }

      Calloc1.c - Calloc1.exe
      • Allocation par calloc d'un tableau de 500000 int
      • Vérification par comptage que les 500000 valeurs sont bien égales à 0
      Allocation de 500000 int
      Allocation reussie a l'adresse : 00000178210C6040

      500000 valeurs egales a 0 pour 500000 attendu
  • realloc
    • Réallocation dynamique d'une zone mémoire déjà allouée dynamiquement avec changement du nombre d'octets alloués
    • Conservation des valeurs des octets de l'ancienne zone dans les octets de la nouvelle zone
    • Syntaxe
      void* realloc(void *ptr, size_t nombreOctets);
      Réallocation dynamique de la zone pointée par ptr pour obtenir une zone allouée dynamiquement de nombreOctets octets contigus trouvée dans le tas
      Si ptr = NULL, allocation ex nihilo d'une nouvelle zone
      Si un simple agrandissement est possible, agrandissement de la zone, sinon, allocation d'une nouvelle zone, copie des octets de la zone initiale dans la nouvelle zone, libération de l'ancienne zone et retour de l'adresse de la nouvelle zone
      Si nouvelle taille plus grande qu'ancienne taille, non initialisation des octets supplémentaires
      Si nouvelle taille plus petite qu'ancienne taille, pertes des octets surnuméraires
      Retour de l'adresse du premier octet de la zone d'octets après réallocation (ancienne ou nouvelle), NULL si échec
      En cas d'échec, non libération de l'ancienne zone
    • Exemple
    • #include <stdio.h>
      #include <stdlib.h>

      void afficherTableauInt(int* t, size_t taille) {
        for (int i = 0; i < taille; i++) {
          printf("%10d", t[i]);
        }
        printf("\n");
      }

      int main(void) {
        size_t taille1 = 3;
        size_t taille2 = 5;
        size_t taille3 = 2;
        int* ptr = (int*) malloc(taille1 * sizeof(int));
        int* tmp;
        if (ptr == NULL) {
          printf("Allocation avortee\n");
          exit(1);
        }
        printf("Allocation reussie a l'adresse   : %p\n", ptr);
        for (int i = 0; i < taille1; i++) {
          ptr[i] = i;
        }
        afficherTableauInt(ptr, taille1);
        printf("\n");
        tmp = (int*) realloc(ptr, taille2 * sizeof(int));
        if (tmp == NULL) {
          printf("Reallocation avortee\n");
          exit(2);
        }
        ptr = tmp;
        printf("Reallocation reussie a l'adresse : %p\n", ptr);
        afficherTableauInt(ptr, taille2);
        printf("\n");
        tmp = (int*) realloc(ptr, taille3 * sizeof(int));
        if (tmp == NULL) {
          printf("Reallocation avortee\n");
          exit(3);
        }
        ptr = tmp;
        printf("Reallocation reussie a l'adresse : %p\n", ptr);
        afficherTableauInt(ptr, taille3);
        return 0;
      }

      Realloc1.c - Realloc1.exe
      • Allocation initiale via un malloc d'un tableau de 3 int rempli avec les valeurs 0, 1 et 2
      • Affichage de ce tableau (utilisation d'une fonction développée ad hoc)
      • Réallocation du tableau pour une nouvelle taille de 5 int sans que les 4ème et 5ème valeurs soient affectée -> deux dernières valeurs aléatoires
      • Affichage du tableau
      • Réalllocation du tableau pour une nouvelle taille de 2 int -> perte des trois dernières valeurs
      • Remarque : non utilisation de la syntaxe ptr = realloc(ptr,taille) car elle génère des warnings de compilation liés à un éventuel problème de désallocation (voir ci-dessous)
      • Remarque : utilisation de la fonction void exit(int n) qui entraine la terminaison du programme avec le code de sortie n passé en paramètre
      Allocation reussie a l'adresse   : 00000214988CE720
      0 1 2

      Reallocation reussie a l'adresse : 00000214988CE860
      0 1 2 6029370 7471184

      Reallocation reussie a l'adresse : 00000214988CAA20
      0 1

Désallocation

  • Contrairement à certains langages informatiques (Java, C#...), pas de ramasse-miettes (garbage collector, GC) automatique en C
    -> Pas de processus en tâche fond permettant de libérer de façon automatique les allocations dynamiques de mémoire qui ne servent plus de façon que cette mémoire puisse resservir
    -> Travail à la charge du développeur
    -> Si travail pas fait ou mal fait, non restitution et donc augmentation progressive de la quantité de mémoire utilisée par un programme (et donc par le système d'exploitation) -> cessation d'activité ou plantage inéluctable du programme
  • Seule désallocation réalisée automatiquement : zone mémoire réallouée si un realloc aboutit
  • Une seule fonction déclarée dans l'API standard stdlib.h
    • free
      Syntaxe
      void free(void* ptr);
      Désallocation de la zone mémoire pointée par ptr
    • Exemples : Gestion de la désallocation pour les 4 programmes exemples d'allocation ci-dessus
    • #include <stdio.h>
      #include <stdlib.h>

      void main(void) {
        int* ptr = (int*) malloc(sizeof(int));
        if (ptr == NULL) {
          printf("Allocation avortee\n");
        }
        else {
          printf("Allocation reussie a l'adresse : %p\n", ptr);
        }
        if (ptr != NULL) {
          printf("Desallocation a l'adresse      : %p\n", ptr);
          free(ptr);
          ptr = NULL;
        }
        else {
          printf("Attention, tentative de desallocation de NULL");
        }
      }

      Malloc1Free.c - Malloc1Free.exe
      • Désallocation uniquement si l'allocation a réussi -> test sur ptr avant free et affectation de ptr à NULL juste après free
      Allocation reussie a l'adresse : 000001A0D8C5AA60 Desallocation a l'adresse      : 000001A0D8C5AA60

      #include <stdio.h>
      #include <stdlib.h>

      void desallocation(void* ptr) {
        if (ptr != NULL) {
          printf("Desallocation a l'adresse      : %p\n", ptr);
          free(ptr);
        }
        else {
          printf("Attention, tentative de desallocation de NULL");
        }
      }

      void main(void) {
        size_t taille1 = 5;
        printf("Allocation de %zu octets ", taille1 * sizeof(double));
        printf("pour un tableau de %zu double\n", taille1);
        double* ptr1 = (double*) malloc(taille1 * sizeof(double));
        if (ptr1 == NULL) {
          printf("Allocation avortee\n");
        }
        else {
          printf("Allocation reussie a l'adresse : %p\n", ptr1);
        }
        size_t taille2 = 80 * (1024ULL * 1024ULL * 1024ULL);
        double* ptr2 = (double*) malloc(taille2);
        printf("Tentative d'allocation de %zu octets ", taille2);
        printf("soit %zu Go\n", taille2 >> 30);
        if (ptr2 == NULL) {
          printf("Allocation avortee\n");
        }
        else {
          printf("Allocation reussie a l'adresse : %p\n", ptr2);
        }
        printf("\n");
        desallocation(ptr1);
        ptr1 = NULL;
        desallocation(ptr2);
        ptr2 = NULL;
      }

      Malloc2Free.c - Malloc2Free.exe
      • Développement d'une fonction dédiée à la désallocation
        • Un paramètre : le pointeur à désallouer
        • Pas de retour (possible de mettre un booléen "réussite")
        • Test du pointeur vis à vis de NULL avant désallocation
        • Affichage de message de désallocation ou d'erreur de désallocation
      Allocation de 40 octets pour un tableau de 5 double
      Allocation reussie a l'adresse : 0000024F9ED1E290
      Tentative d'allocation de 85899345920 octets soit 80 Go
      Allocation avortee

      Desallocation a l'adresse : 0000024F9ED1E290
      Attention, tentative de desallocation de NULL

      #include <stdio.h>
      #include <stdlib.h>

      void desallocation(void* ptr) {
        if (ptr != NULL) {
          printf("Desallocation a l'adresse      : %p\n", ptr);
          free(ptr);
        }
        else {
          printf("Attention, tentative de desallocation de NULL");
        }
      }

      void main(void) {
        size_t taille = 500000;
        int* ptr = (int*) calloc(taille, sizeof(int));
        if (ptr == NULL) {
          printf("Allocation avortee\n");
        }
        else {
          printf("Allocation reussie a l'adresse : %p\n", ptr);
        }
        desallocation(ptr);
        ptr = NULL;
      }

      Calloc1Free.c - Calloc1Free.exe
      • Utilisation de la même fonction de désallocation que l'exemple précédent
      Allocation reussie a l'adresse : 0000016F60955040 Desallocation a l'adresse      : 0000016F60955040

      #include <stdio.h>
      #include <stdlib.h>

      void desallocation(void* ptr) {
        if (ptr != NULL) {
          printf("Desallocation a l'adresse        : %p\n", ptr);
          free(ptr);
        }
        else {
          printf("Attention, tentative de desallocation de NULL");
        }
      }

      int main(void) {
        size_t taille1 = 3;
        size_t taille2 = 5;
        size_t taille3 = 2;
        int* ptr = (int*) malloc(taille1 * sizeof(int));
        int* tmp;
        if (ptr == NULL) {
          printf("Allocation avortee\n");
          exit(1);
        }
        printf("Allocation reussie a l'adresse   : %p\n", ptr);
        for (int i = 0; i < taille1; i++) {
          ptr[i] = i;
        }
        tmp = (int*) realloc(ptr, taille2 * sizeof(int));
        if (tmp == NULL) {
          printf("Reallocation avortee\n");
          desallocation(ptr);
          ptr = NULL;
          exit(2);
        }
        ptr = tmp;
        printf("Reallocation reussie a l'adresse : %p\n", ptr);
        tmp = (int*) realloc(ptr, taille3 * sizeof(int));
        if (tmp == NULL) {
          printf("Reallocation avortee\n");
          desallocation(ptr);
          ptr = NULL;
          exit(3);
        }
        ptr = tmp;
        printf("Reallocation reussie a l'adresse : %p\n", ptr);
        desallocation(ptr);
        ptr = NULL;
        return 0;
      }

      Realloc1Free.c - Realloc1Free.exe
      • Spécificité de realloc : libération automatique du pointeur passé en paramètre si la réallocation réussit -> ne pas la faire sinon plantage
      • Une allocation suivie de deux réallocations
      • Utilisation d'une variable pointeur temporaire pour les résultats des réallocations pour conserver le pointeur initial de façon à pouvoir le libérer en cas d'échec de la réallocation
      • Libération d'un seul pointeur car les zones effectivement réallouées sont automatiquement libérées
      • Utilisation de la fonction exit donc libération obligatoire avant tout exit de tout ce qui doit être libéré
      Allocation reussie a l'adresse : 000001CDDAD6E560
      Reallocation reussie a l'adresse : 000001CDDAD6E5A0
      Reallocation reussie a l'adresse : 000001CDDAD6B1C0
      Desallocation a l'adresse : 000001CDDAD6B1C0

Erreurs fréquemment commises par les développeurs

  • Oublier de faire une allocation mémoire
    -> Réalisation de lectures et d'écritures mémoire là où cela ne devrait pas être (pas de contrôle à l'exécution)
    -> Plantage qui peut sembler aléatoire
  • Se tromper dans le nombre d'octets à allouer en n'allouant pas assez de place
    -> Réalisation de lectures et d'écritures mémoires là où cela ne devrait pas être (pas de contrôle à l'exécution)
    -> Plantage qui peut sembler aléatoire
    Exemple : Allouer n octets pour une chaînes de caractères de n caractères alors qu'il en faut n+1
  • Ne pas désallouer certaines zones allouées
    -> Saturation de la mémoire disponible
    -> Plantage qui peut sembler aléatoire
    Solution éventuelle : Implanter l'équivalent un ramasse miettes
  • Désallouer deux fois la même zone mémoire
    -> Plantage qui peut sembler aléatoire
    Solution : Après désallocation, affecter systématiquement les pointeurs désalloués avec NULL, ne désallouer un pointeur que s'il est différent de NULL
  • Pas d'outil standard pour détecter ce type de problèmes
  • Quelques outils non standards mais couramment utilisés
    • valgrind (Unix)
    • ...
  • Préoccupation permanente du développeur : valider la gestion de la mémoire du point de vue des allocations et des désallocations
    Pas toujours simple car il ne faut pas seulement prévoir la ou les situations où tout se passe bien, mais aussi toutes les situations où une allocation échoue voire échoue alors que toutes les allocations précédentes ont abouti

Allocation dynamique et fonctions

  • Possibilité d'utiliser l'allocation dynamique dans les fonctions
  • Attention : pas de désallocation automatique en fin de fonction de ce qui a été alloué au cours de l'exécution de la fonction
  • Possibilité d'utiliser l'allocation dynamique dans une fonction et de faire retourner le pointeur sur la zone allouée en retour de fonction
  • Attention : obligation pour la fonction appelante de réaliser la désallocation réalisée dans la fonction appelée
  • Exemple
  • #include <stdio.h>
    #include <stdlib.h>

    float* creerTableauFloat(size_t n) {
      return (float*)calloc(n, sizeof(float));
    }

    int* creerEtInitialiserTableauInt(size_t n, int val) {
      int* ti = (int*)calloc(n, sizeof(int));
      if (ti != NULL) {
        for (int i = 0; i < n; i++) {
          ti[i] = val;
        }
      }
      return ti;
    }

    void main(void) {
      size_t taille1 = 3;
      size_t taille2 = 5;
      float* tf = creerTableauFloat(taille1);
      if (tf == NULL) {
        printf("Allocation du tableau de flotants avortee\n");
      }
      else {
        printf("Allocation tableau de flotants reussie a l'adresse : %p\n", tf);
        free(tf);
        tf = NULL;
      }
      int* ti = creerEtInitialiserTableauInt(taille2, -1);
      if (ti == NULL) {
        printf("Allocation du tableau d'entiers avortee\n");
      }
      else {
        printf("Allocation du tableau d'entiers reussie a l'adresse : %p\n", ti);
        for (int i = 0; i < taille2; i++) {
          printf("%3d", ti[i]);
        }
        printf("\n");
        free(ti);
        ti = NULL;
      }
    }

    AllocationDynamiqueEtFonctions.c - AllocationDynamiqueEtFonctions.exe

     

     

Pointeur sur pointeur et allocation dynamique

  • Possibilité de définir des variables de type pointeur sur pointeur sur type permettant ainsi de gérer des tableaux dynamiques à 2 dimensions (à deux indices)
  • Exemple : int** t;
  • Usuellement, premier indice = numéro de ligne, second indice = numéro de colonne
  • Pointeur de pointeur = tableau unidimensionnel de taille n de pointeurs = tableau unidimensionnel de taille n de tableaux unidimensionnels de tailles constantes ou variables  = matrice si tailles constantes pour les lignes
    • Premier niveau de pointeur : tableau de n pointeurs sur type alloué dynamiquement
    • Second niveau de pointeur : n tableaux de type alloués dynamiquement avec la taille souhaitée pour chaque ligne définissant ainsi le nombre d'éléments stockables sur la ligne
    • Valeurs de chaque ligne de la matrices stockées contiguement en mémoire (allocation de chaque ligne en une seule allocation)
    • Lignes de valeurs non forcément stockées contiguement en mémoire (autant d'allocations que de lignes)
  • Généralisable aux dimensions supérieures
  • Exemple
  • #include <stdio.h>
    #include <stdlib.h>

    void main(void) {
      size_t nbL = 5;
      size_t nbC = 500000;
      printf("Allocation de %zu x %zu int\n", nbL, nbC);
      int** ti =(int**) calloc(nbL, sizeof(int*));
      for (int i = 0; i < nbL; i++) {
        ti[i] = (int*) calloc(nbC, sizeof(int));
      }
      for (int i = 0; i < nbL; i++) {
        printf("%p\n", ti[i]);
      }
      printf("\n");
      printf("Desallocation\n");
      if (ti != NULL) {
        for (int i = 0; i < nbL; i++) {
          free(ti[i]);
          ti[i] = NULL;
        }
        free(ti);
        ti = NULL;
      }
    }

    PointeurSurPointeur1.c - PointeurSurPointeur1.exe
    • Définition d'une variable de type int**
    • Allocation dynamique
      • Pour le premier niveau, allocation dynamique pour nbL valeurs de type int*
      • Pour le second second niveau, pour chacune des nbL lignes, allocation dynamique pour ncC valeurs de type int
      • Obtention d'une matrice permettant de stocker nbL * nbC int
    • Affichage des valeurs des nbL pointeurs de ligne
    • Désallocation
      • Pour le second niveau, pour chacune des nbL lignes, libération du tableau int* de la ligne
      • Pour le premier niveau, libération du tableau int**
      • Attention à bien opérer dans l'ordre second niveau puis premier niveau car premier niveau utilisé pour libérer le second niveau
    • Problèmes
      • En cours d'allocation, non gestion du fait que l'allocation du tableau int** peut échouer
      • En cours d'allocation, non gestion du fait que l'allocation d'un (ou de plusieurs) des tableaux int* peut échouer
      • Allocation possiblement non fonctionnelle dans certains cas
      • Solution : voir ci-dessous
    Allocation de 5 x 500000 int
    000001E6C7A1D040
    000001E6C7C1F040
    000001E6C7E15040
    000001E6C8001040
    000001E6C81FE040

    Desallocation

    #include <stdio.h>
    #include <stdlib.h>

    int main(void) {
      size_t nbL = 100;
      size_t nbC = 500000000;
      printf("Tentative d'allocation pour %zu x %zu entiers\n", nbL, nbC);
      printf("soit %zu octets (environ %zu Go)\n",
             nbL * nbC * sizeof(int),
             (nbL * nbC * sizeof(int)) >> 30);
      int** ti =(int**) calloc(nbL, sizeof(int*));
      if (ti != NULL) {
        int i = 0;
        while ((i < nbL) && ((ti[i] = (int*) calloc(nbC, sizeof(int))) != NULL)) {
          i++;
          printf("+");
        }
        printf("\n");
        if (ti[nbL - 1] == NULL) {
          printf("Allocation impossible a la ligne %d/%zu\n", i + 1, nbL);
          printf("Desallocation de ce qui avait ete alloue\n");
          for (int j = 0; j < i; j++) {
            free(ti[j]);
            ti[j] = NULL;
            printf("-");
          }
          printf("\n");
          free(ti);
          ti = NULL;
        }
      }
      printf("\n");
      if (ti != NULL) {
        printf("Allocation reussie\n");
      }
      else {
        printf("Allocation avortee\n");
      }
      if (ti != NULL) {
        for (int i = 0; i < nbL; i++) {
          free(ti[i]);
          ti[i] = NULL;
        }
        free(ti);
        ti = NULL;
        printf("Desallocation terminee\n");
      }
      return 0;
    }

    PointeurSurPointeur2.c - PointeurSurPointeur2.exe
    • Modifications de la partie allocation
      • Test de la bonne allocation du tableau de niveau 1 pour le cas où il n'aurait pas pu être alloué
      • Transformation du for d'allocation des lignes en un while pour permettre l'interruption des allocations sitôt qu'une d'elles échoue et repèrage de la ligne de l'échec
      • Si échec, libération de toutes les lignes déjà allouées et libération du tableau de niveau 1
    • Pas de modification sur la dernière partie réalisant la désallocation du tableau de tableaux normalement alloué
    • Problème
      • Programmation peu réutilisable
      • Solution : développer des fonctions, voir ci-dessous
    Tentative d'allocation pour 100 x 500000000 entiers
    soit 200000000000 octets (environ 186 Go)
    +++++++++++++++++++++++++++++++++++++++++++
    Allocation impossible a la ligne 44/100
    Desallocation de ce qui avait ete alloue
    -------------------------------------------

    Allocation avortee

    #include <stdio.h>
    #include <stdlib.h>

    int** creerMatrice(size_t nbLignes, size_t nbColonnes) {
      int** ti = (int**) calloc(nbLignes, sizeof(int*));
      if (ti != NULL) {
        int i = 0;
        while ((i < nbLignes) && 
               ((ti[i] = (int*) calloc(nbColonnes, sizeof(int))) != NULL)) {
          i++;
        }
        if (ti[nbLignes - 1] == NULL) {
          for (int j = 0; j < i; j++) {
            free(ti[j]);
            ti[j] = NULL;
          }
          free(ti);
          ti = NULL;
        }
      }
      return ti;
    }

    void detruireMatrice(int** ti, size_t nbLignes, size_t nbColonnes) {
      if (ti != NULL) {
        for (int i = 0; i < nbLignes; i++) {
          free(ti[i]);
          ti[i] = NULL;
        }
        free(ti);
        ti = NULL;
      }
    }

    void test(size_t nbL, size_t nbC) {
      printf("Tentative d'allocation pour %zu x %zu entiers\n", nbL, nbC);
      printf("soit %zu octets (environ %.2lf Go)\n",
             nbL * nbC * sizeof(int),
             ((nbL * nbC * sizeof(int)) >> 20)/1024.0);
      int** ti = creerMatrice(nbL, nbC);
      if (ti != NULL) {
        printf("Allocation reussie\n");
        detruireMatrice(ti, nbL, nbC);
        ti = NULL;
        printf("Desallocation terminee\n");
      }
      else {
        printf("Allocation avortee\n");
      }
    }

    void main(void) {
      test(100, 500000000);
      printf("\n");
      test(1000, 500000);
    }

    PointeurSurPointeur3.c - PointeurSurPointeur3.exe
    • Développement d'une fonction pour l'allocation d'une matrice d'int
      • Deux paramètres : nombre de lignes et nombre de colonnes
      • Un retour le pointeur int** sur la matrice allouée
    • Développement d'une fonction pour la désallocation d'une matrice d'int
      • Trois paramètres : le pointeur int** correspondant à la matrice à désallouer, son nombre de lignes et son nombre de colonnes (informations que la fonction ne peut pas déterminer car elle reçoit des pointeurs)
      • Aucun retour
    • Une fonction de test pour valider les deux premières selon les scenarii souhaités
    Tentative d'allocation pour 100 x 500000000 entiers
    soit 200000000000 octets (environ 186.26 Go)
    Allocation avortee

    Tentative d'allocation pour 1000 x 500000 entiers
    soit 2000000000 octets (environ 1.86 Go)
    Allocation reussie
    Desallocation terminee