Gestion dynamique de la mémoire en C

Apprends comment un programme C obtient de la mémoire pendant son exécution et la restitue. Compare la pile et le tas, alloue des blocs avec malloc et calloc, redimensionne-les avec realloc et libère-les avec free. Reconnais les fuites de mémoire, les pointeurs pendants, les doubles libérations et les accès hors limites, comprends pourquoi ils provoquent des plantages comme les erreurs de segmentation, puis repère-les et corrige-les avec Valgrind et AddressSanitizer.

DébutantUnité 5 sur 8 de la formation Formation : Langage C : pointeurs, mémoire et structures

Ce que tu vas apprendre

  1. Décrire la pile, le tas et la mémoire statique, la durée de vie des données dans chacun et les situations où un programme a besoin de mémoire dans le tas.
    • Comparer la pile, le tas et le stockage statique
      Décrire les différences de durée de vie et de gestion entre le stockage automatique, statique et alloué, ainsi que ce qui arrive à chacun lors des appels de fonctions.
    • Déterminer quand stocker les données dans le tas
      Choisir entre variables locales et allocation dans le tas selon la taille, la durée de vie et la croissance des données, et éviter de renvoyer l’adresse d’une variable locale.
  2. Réserver des blocs de la bonne taille avec malloc et calloc, vérifier que chaque demande a réussi et libérer chaque bloc exactement une fois sous la responsabilité d’un propriétaire clairement désigné.
    • Allouer de la mémoire avec malloc et sizeof
      Calculer le nombre d’octets à demander, appeler malloc, vérifier l’échec éventuel et utiliser le bloc comme un tableau.
    • Obtenir de la mémoire mise à zéro avec calloc
      Utiliser calloc pour allouer des tableaux dont les éléments valent initialement zéro, et savoir quand cette fonction est préférable à malloc.
    • Libérer la mémoire avec free et suivre la propriété des allocations
      Libérer chaque bloc exactement une fois au bon moment, décider quelle partie du programme en est responsable et effectuer correctement le nettoyage lorsqu’une allocation échoue.
  3. Agrandir et réduire des blocs du tas sans perdre de données ni de mémoire, et construire des tableaux qui s’étendent à mesure que les données arrivent.
    • Agrandir et réduire un bloc avec realloc en toute sécurité
      Utiliser un pointeur temporaire avec realloc, préserver les données et le bloc d’origine en cas d’échec, et mettre à jour tous les pointeurs après un déplacement du bloc.
    • Construire un tableau extensible
      Conserver une longueur et une capacité avec le pointeur, faire croître la capacité de façon géométrique et lire des données de longueur inconnue dans un tampon extensible.
  4. Identifier les fuites de mémoire, les pointeurs pendants, les doubles libérations, les accès hors limites et les lectures de valeurs non initialisées dans du code C, expliquer leurs dangers et retrouver la cause d’une erreur de segmentation.
    • Repérer et prévenir les fuites de mémoire
      Expliquer ce qu’est une fuite, repérer les constructions de code qui en provoquent et adopter des habitudes qui les évitent.
    • Éviter les pointeurs pendants, les utilisations après libération et les doubles libérations
      Reconnaître les pointeurs vers une mémoire dont la durée de vie est terminée, expliquer pourquoi leur utilisation ou une nouvelle libération provoque un comportement indéfini, et adopter des habitudes qui l’évitent.
    • Respecter les limites du bloc et éviter les débordements et les lectures de valeurs non initialisées
      Maintenir chaque accès dans les bornes de son bloc, dimensionner correctement les tampons de chaînes et initialiser la mémoire avant de la lire.
    • Expliquer les erreurs de segmentation et retrouver leur origine
      Expliquer ce qu’est une erreur de segmentation, citer ses causes fréquentes et remonter du plantage à la ligne fautive.
  5. Exécuter des programmes avec Memcheck de Valgrind et AddressSanitizer, lire leurs rapports et s’en servir pour corriger les fuites et les accès invalides jusqu’à obtenir une exécution sans erreur.
    • Vérifier un programme avec Valgrind Memcheck
      Compiler un programme pour Valgrind, l’exécuter avec la recherche de fuites et lire ses messages d’erreur, son bilan du tas et ses catégories de fuites.
    • Utiliser AddressSanitizer et LeakSanitizer
      Compiler un programme avec AddressSanitizer, lire ses rapports et choisir entre les sanitizers et Valgrind.
    • Corriger les bogues de mémoire à partir du rapport d’un outil
      Suivre une procédure reproductible pour remonter du rapport d’un outil à la ligne fautive, corriger la cause plutôt que le symptôme et confirmer la correction par une exécution sans erreur.

Gestion dynamique de la mémoire en C : cours

5 chapitres

1

Expliquer où un programme C stocke ses données

Chaque objet a une durée de vie et un pointeur n’est utile que tant que sa cible existe. Les variables locales, les données statiques et les blocs alloués suivent des règles de durée de vie différentes. Relier ces règles à la pile et au tas explique pourquoi une fonction peut renvoyer un tableau alloué, mais ne peut pas renvoyer une adresse utilisable de son propre tableau local. La taille, la durée de vie et la croissance ultérieure guident le choix du stockage adapté à chaque besoin.

1.1 Stockage et durée de vie

L’adresse d’une variable indique où se trouvent ses données. Sa durée de stockage indique combien de temps ces données existent. Conserver une adresse ne prolonge pas la vie de l’objet.

Trois durées de vie
Durée de stockageObjets typiquesFin de la durée de vie
AutomatiqueVariables locales ordinaires et paramètresÀ la fin de leur bloc
StatiqueVariables globales et variables locales déclarées staticÀ la fin du programme
AllouéeBlocs obtenus auprès des fonctions d’allocationÀ la libération du bloc

Sur les systèmes courants, les objets automatiques utilisent la pile d’appels et les blocs alloués utilisent le tas. Le C définit les durées de vie ; il n’impose pas ces régions physiques. Le code machine et les données statiques occupent d’autres régions, dont la disposition dépend du système.

Un appel de fonction crée normalement un cadre de pile contenant ses paramètres, ses variables locales et les informations de retour. Les appels s’imbriquent : le cadre le plus récent est retiré en premier. Le retour de la fonction libère le stockage automatique de cet appel, ce qui permet aux appels ultérieurs de réutiliser l’espace. L’allocateur du tas gère une réserve distincte. Il réserve un bloc contigu sur demande et fournit son adresse de début. Le retour d’une fonction ne libère pas ce bloc.

Le pointeur et son bloc sont des objets distincts

Considère cette fonction, déclarée avec <stdlib.h> disponible :

int *make_array(size_t n) {
    if (n == 0 || n > SIZE_MAX / sizeof(int)) return NULL;
    int *p = malloc(n * sizeof *p);
    if (p == NULL) return NULL;
    return p;
}

SIZE_MAX provient de <stdint.h>. Lorsque main appelle int *nums = make_array(5);, le paramètre n et la variable pointeur p appartiennent à l’appel. Ce n’est pas le cas du tableau alloué. Là où un int occupe 4 octets, le bloc contient 20 octets. Le retour copie son adresse dans nums ; il ne copie pas le tableau. La fonction appelante vérifie nums, utilise les éléments après leur avoir affecté une valeur, puis libère le bloc.

La variable locale p cesse d’exister au retour de la fonction. Le bloc subsiste, car sa durée de stockage est allouée et non automatique. Cette distinction fonctionne aussi dans l’autre sens : un pointeur local peut pointer vers un objet global et un pointeur global peut pointer vers un bloc du tas.

Les données statiques survivent aux appels
int next_id(void) {
    static int id = 0;
    return ++id;
}

Les appels successifs renvoient 1, 2 et 3. L’initialiseur s’exécute une fois et id existe pendant toute l’exécution du programme. Si l’on retire static, chaque appel crée une nouvelle variable automatique id, initialisée à zéro ; chaque appel renvoie alors 1.

L’espace de la pile est limité

Un grand tableau local et des appels profondément imbriqués consomment tous deux de l’espace dans la pile. Dépasser la limite provoque un débordement de pile, qui met généralement fin au programme. De nombreuses configurations Linux autorisent une pile de 8 MiB ; la réserve par défaut de Microsoft pour la pile du thread principal est généralement de 1 MiB. Ce sont des paramètres système, pas des règles du C. Un tableau local de 4 000 000 d’entiers de type int nécessite 16 000 000 octets là où un int occupe 4 octets, soit environ 15,3 MiB.

L’allocation dans le tas entraîne des coûts de gestion lors de la réservation et de la libération des blocs. Lire un élément n’est pas intrinsèquement plus lent simplement parce que son tableau se trouve dans le tas. Par ailleurs, la réserve de mémoire du tas n’a aucun rapport avec la structure de données appelée tas, utilisée dans une file de priorité.

Points clés

  • La durée de stockage d’un objet détermine sa durée de vie.
  • Les variables locales automatiques cessent d’exister à la fin de leur bloc ; les objets statiques existent pendant toute l’exécution du programme.
  • Un pointeur local et le bloc du tas vers lequel il pointe ont des durées de vie distinctes.
  • Les grands tableaux locaux et les appels profondément imbriqués peuvent épuiser la pile.
  • Le C spécifie les durées de vie, tandis que la pile et le tas décrivent des implémentations courantes.

1.2 Choisir le stockage adapté au besoin

Choisis le stockage en te demandant combien d’espace est nécessaire, quand cette taille sera connue et combien de temps les données devront exister. La solution adaptée la plus simple est souvent un tableau local ordinaire.

Adapter le stockage aux données
BesoinPremier choix adapté
Un petit tableau de taille fixe utilisé pendant un appelTableau local
Un nombre lu dans les données d’entréeAllocation vérifiée dans le tas
Un résultat qui survit à la fonction qui le créeAllocation dans le tas ou tampon appartenant à la fonction appelante
Des données qui grandissent à mesure qu’elles arriventTampon redimensionnable dans le tas
Un très grand tableauAllocation dans le tas avec vérification des échecs

Une semaine de températures quotidiennes nécessite sept valeurs : double temps[7]; est donc un choix naturel de tableau local. Pour une note par étudiant, la taille doit être obtenue à l’exécution. Avec n étudiants, utilise double *grades = malloc(n * sizeof *grades); après avoir vérifié la taille. Pour 250 étudiants et des valeurs double de 8 octets, cela demande 2 000 octets. Vérifie ensuite le résultat avant de remplir le tableau.

Pourquoi renvoyer un tampon local échoue
char *greeting(void) {
    char buf[16] = "hello";
    return buf;  /* error: buf ends when this call returns */
}

L’adresse renvoyée ne peut pas prolonger la durée de vie de buf. La fonction appelante reçoit un pointeur pendant, même si les anciens octets semblent inchangés. GCC et Clang peuvent émettre un avertissement pour ce type de code.

Une solution consiste à laisser la fonction appelante fournir le stockage :

#include <string.h>

void greeting(char out[16]) {
    strcpy(out, "hello");
}

/* In the caller: */
char message[16];
greeting(message);

Ici, le tableau de la fonction appelante reste en vie pendant tout l’appel et après celui-ci. Le paramètre se comporte toujours comme un pointeur ; la fonction appelante doit fournir suffisamment d’espace. Une deuxième solution alloue un résultat dans le tas et rend la fonction appelante responsable de sa libération. Une troisième renvoie un tampon statique. Ce tampon subsiste, mais toutes les fonctions appelantes le partagent et un appel ultérieur peut écraser son contenu. Choisis selon que les fonctions appelantes ont besoin ou non de résultats indépendants.

Une taille connue à l’exécution n’impose pas toujours le tas

C99 a introduit les tableaux de longueur variable, comme int a[n];. Leur durée de stockage est automatique et ils se trouvent généralement dans la pile. C11 a rendu leur prise en charge facultative et le compilateur de Microsoft ne les prend pas en charge. Une grande valeur saisie peut épuiser la pile et la déclaration du tableau ne fournit aucun résultat nul à vérifier. L’allocation dans le tas permet de vérifier les échecs et prend en charge le redimensionnement ultérieur.

Un pointeur n’indique pas la propriété
int value = 3;
int *p = &value;

p pointe vers un objet automatique. Le passer à free est invalide. La même règle interdit de libérer un tableau statique ou un littéral de chaîne. L’adresse doit appartenir à une allocation encore active dont le programme est responsable.

De même, char *s = "hello"; pointe vers un littéral de chaîne ; cette instruction ne crée pas de copie modifiable. Modifier le littéral provoque un comportement indéfini. Utilise char s[] = "hello"; pour obtenir une copie locale modifiable, ou alloue de l’espace pour une copie distincte dans le tas.

Points clés

  • Choisis le stockage selon la taille des données, leur durée de vie nécessaire et leur besoin de croissance.
  • Une adresse renvoyée par une fonction n’est utilisable que si sa cible existe encore.
  • Avec un tampon fourni par la fonction appelante, celle-ci reste responsable de sa libération.
  • Un tampon de résultat statique est partagé entre les appels.
  • Les pointeurs peuvent viser des objets locaux, statiques ou alloués ; seuls les blocs alloués encore actifs peuvent être libérés.
  • Les littéraux de chaîne ne sont pas des copies modifiables.
2

Allouer et libérer de la mémoire dans le tas

Une demande d’allocation nécessite un nombre d’octets, un traitement en cas d’échec et un responsable de la libération. Pars du nombre d’éléments pour déterminer l’espace nécessaire, décide si leurs valeurs initiales doivent être nulles et utilise chaque bloc uniquement après une demande réussie. La propriété relie ensuite la dernière utilisation au nettoyage, notamment pour les allocations imbriquées et les fonctions dont une partie seulement des opérations réussit.

2.1 Demander le bon nombre d’octets

malloc réserve un bloc contigu d’octets et renvoie son adresse de début. Inclus <stdlib.h> pour obtenir sa déclaration. Une allocation réussie fournit un espace de stockage, mais ne le remplit pas de valeurs utiles.

Compter les éléments, puis déterminer leur taille
int *nums = malloc(n * sizeof *nums);

n est un nombre d’éléments ; sizeof *nums est le nombre d’octets par élément. Leur produit donne le nombre d’octets. Là où un int occupe 4 octets, dix entiers de type int nécessitent 40 octets. Dix valeurs double de 8 octets nécessitent 80 octets. Dix pointeurs nécessitent 80 octets là où chaque pointeur occupe 8 octets. Un char occupe toujours un octet C.

La forme sizeof *nums suit le type pointé si la déclaration change. Pour ce type ordinaire qui n’est pas de longueur variable, sizeof n’évalue pas *nums et ne déréférence donc pas un pointeur non initialisé. En C, le void * renvoyé est converti en type pointeur vers un objet sans conversion explicite.

malloc(n) alloue n octets, pas n entiers de type int. sizeof nums mesure la variable pointeur, pas un élément ni le bloc entier. Sur un système où les pointeurs occupent 8 octets et les entiers de type int 4 octets, malloc(n * sizeof nums) demande deux fois l’espace nécessaire, ce qui masque l’erreur dans l’expression. Avec d’autres types, l’espace alloué peut être insuffisant.

Vérifier la taille et l’échec avant utilisation

Ce programme complet rejette une demande vide, protège la multiplication contre le débordement, vérifie l’allocation et initialise chaque élément :

#include <stdint.h>
#include <stdlib.h>

int main(void) {
    size_t n = 5;
    int *nums;
    if (n == 0 || n > SIZE_MAX / sizeof *nums) {
        return EXIT_FAILURE;
    }
    nums = malloc(n * sizeof *nums);
    if (nums == NULL) return EXIT_FAILURE;

    for (size_t i = 0; i < n; ++i) {
        nums[i] = (int)(i * i);
    }
    free(nums);
    return EXIT_SUCCESS;
}

SIZE_MAX, défini dans <stdint.h>, est la plus grande valeur de size_t. Vérifie par une division avant la multiplication. Si le produit reboucle d’abord, vérifier le résultat réduit après rebouclage ne permet pas de retrouver la taille voulue. Une demande non nulle peut échouer même lorsque son calcul est représentable : la vérification du pointeur nul est donc une étape distincte.

Les valeurs initialisées ici sont 0, 1, 4, 9 et 16. nums[i] accède au même élément que *(nums + i). L’arithmétique des pointeurs avance par éléments : le code ne multiplie donc pas i par la taille en octets.

Conserver le nombre d’éléments avec le pointeur

Aucune opération standard du C ne permet de retrouver le nombre d’éléments d’une allocation à partir de son pointeur. Stocke n séparément et passe le pointeur et le nombre aux fonctions. Ce nombre contrôle les boucles et les vérifications d’indices.

Une chaîne copiée nécessite ses caractères et le caractère de fin '\0'. La chaîne « Amsterdam » comporte neuf caractères et nécessite dix octets : demande donc strlen(s) + 1 pour sa copie. <string.h> fournit strlen.

Une demande malloc de taille nulle peut renvoyer un pointeur nul ou un pointeur qui peut être libéré mais pas déréférencé. Traite explicitement le cas où le nombre d’éléments est nul comme un cas vide, au lieu de considérer un tel pointeur comme un espace utilisable pour un tableau.

Points clés

  • Pour obtenir la taille d’un tableau en octets, multiplie le nombre d’éléments par sizeof *pointer.
  • Vérifie que la multiplication ne déborde pas avant d’appeler malloc.
  • Vérifie que le résultat n’est pas NULL avant de l’utiliser.
  • Initialise chaque élément alloué avec malloc avant de le lire.
  • Conserve séparément le nombre d’éléments ; sizeof pointer ne permet pas de le retrouver.
  • Inclus le caractère nul de fin de chaîne lors de l’allocation d’une copie de chaîne.
  • Traite explicitement le cas où le nombre d’éléments est nul.

2.2 Initialiser un tableau à zéro

Un compteur a besoin d’une valeur initiale définie avant la première incrémentation. calloc combine allocation et mise à zéro : un tableau de compteurs entiers est donc prêt à compter après la vérification de l’allocation.

Deux arguments décrivent le tableau

Inclus <stdlib.h> et écris :

int *tally = calloc(26, sizeof *tally);
if (tally == NULL) return EXIT_FAILURE;

Le premier argument est le nombre d’éléments et le second la taille d’un élément. Là où un int occupe 4 octets, cela réserve 104 octets. Chaque octet commence à zéro : chaque compteur entier commence donc à zéro. Appeler calloc avec un seul argument combiné ne compile pas ; son interface exige deux arguments.

Pour le mot « banana », incrémente le compteur correspondant à chaque lettre. Après son traitement, les compteurs de a, b et n contiennent 3, 1 et 2. Tous les autres restent à zéro. Avec un encodage où les lettres minuscules sont consécutives, l’expression d’indice est c - 'a'. La donnée d’entrée doit être une lettre minuscule avant que cette expression soit utilisée comme indice de tableau.

#include <stdlib.h>

int main(void) {
    int *counts = calloc(3, sizeof *counts);
    if (counts == NULL) return EXIT_FAILURE;
    counts[1]++;
    counts[1]++;
    /* The counters now hold 0, 2, 0. */
    free(counts);
    return EXIT_SUCCESS;
}

Le code de comptage n’a écrit que dans le compteur du milieu. Ses deux voisins contiennent toujours des valeurs utiles parce que l’allocation les a mis à zéro.

Ce que signifie une mémoire mise à zéro

calloc met chaque bit à zéro. Cela donne la valeur zéro aux éléments entiers et char. Un tampon de caractères de taille positive mis à zéro contient donc initialement une chaîne vide : son premier caractère est '\0'.

Sur les plateformes courantes, les bits tous à zéro représentent aussi le zéro à virgule flottante et les pointeurs nuls. Le C portable ne garantit pas ces représentations pour les types à virgule flottante et les types pointeurs. Si le code a besoin de telles valeurs sur toutes les implémentations C, affecte explicitement 0.0 ou NULL aux éléments.

Choisir entre les fonctions d’allocation
Utilisation initialeChoix naturel
Incrémenter des compteurs entierscalloc
Modifier progressivement des indicateurs entiers initialement tous à zérocalloc
Écraser immédiatement chaque élémentmalloc

Un appel réussi à malloc suivi de memset peut mettre un bloc à zéro, mais la multiplication qui calcule la taille dans ce code doit toujours être protégée contre le débordement. Les implémentations courantes de calloc rejettent un produit trop grand pour size_t ; C23 l’exige explicitement. Le résultat doit toujours être vérifié, car une demande de taille correcte peut échouer et renvoyer un pointeur nul.

La mise à zéro s’applique à cette allocation. Si realloc agrandit ensuite le bloc, les octets ajoutés ne sont pas initialisés. Affecte une valeur à ces éléments avant de les incrémenter ou de les lire. Enfin, libère le bloc avec free, comme pour un bloc obtenu avec malloc.

Points clés

  • Passe à calloc un nombre d’éléments et une taille d’élément.
  • Les compteurs entiers mis à zéro peuvent être incrémentés dès que l’allocation a été vérifiée.
  • Des octets entièrement à zéro ne remplacent pas de façon portable l’affectation de zéro à virgule flottante ou de pointeurs nuls.
  • Un agrandissement avec realloc ne met pas les octets ajoutés à zéro.

2.3 Un responsable et une libération finale

Pour chaque bloc alloué, il faut répondre clairement à la question « Qui le libère ? ». Cette responsabilité est la propriété. Une autre partie du code peut emprunter l’adresse sans obtenir l’autorisation de libérer le bloc.

Libérer après la dernière utilisation

free, déclarée dans <stdlib.h>, rend un bloc alloué encore actif disponible pour réutilisation. Passe son adresse de début exactement une fois. free(NULL) ne fait rien. Passer une adresse locale, un littéral de chaîne, une adresse intérieure comme p + 1 ou un bloc déjà libéré provoque un comportement indéfini.

free(p) n’affecte aucune valeur à la variable p et ne garantit pas l’effacement des octets. Après la libération, l’ancienne valeur du pointeur est invalide. Si la variable reste utilisée, affecte-lui p = NULL; sans examiner son ancienne valeur. Cela ne modifie que p ; les alias doivent eux aussi cesser d’utiliser le bloc libéré.

Une fonction qui renvoie de la mémoire nouvellement allouée en transfère la propriété à la fonction appelante :

#include <stdlib.h>
#include <string.h>

char *copy_string(const char *s) {
    char *copy = malloc(strlen(s) + 1);
    if (copy == NULL) return NULL;
    strcpy(copy, s);
    return copy;
}

La fonction appelante vérifie le résultat, utilise la copie et la libère. La chaîne d’entrée est empruntée ; cette fonction ne la libère pas. POSIX et C23 proposent strdup selon le même principe de propriété ; la fonction ci-dessus fonctionne aussi en C17.

Libérer les allocations imbriquées de l’intérieur vers l’extérieur

Une grille constituée d’un tableau de pointeurs de lignes comporte des blocs distincts. Trois pointeurs de lignes et trois lignes de quatre entiers de type int nécessitent quatre allocations. Avec des pointeurs de 8 octets et des entiers de type int de 4 octets, le bloc extérieur utilise 24 octets et les lignes 48 octets, soit 72 octets au total.

Libère chaque ligne tant que son adresse est encore disponible, puis libère le tableau de pointeurs. Libérer le bloc extérieur ne libère pas récursivement ses lignes. Accéder à grid[i] après avoir libéré grid utilise une zone de stockage libérée.

Nettoyer après une réussite partielle

Cet exemple complet suit le nombre de lignes effectivement créées :

#include <stdlib.h>

int main(void) {
    size_t rows = 3, made = 0;
    int **grid = malloc(rows * sizeof *grid);
    if (grid == NULL) return EXIT_FAILURE;

    for (; made < rows; ++made) {
        grid[made] = malloc(4 * sizeof *grid[made]);
        if (grid[made] == NULL) break;
    }
    int status = made == rows ? EXIT_SUCCESS : EXIT_FAILURE;
    /* Use the grid here only if every row was allocated. */
    for (size_t i = 0; i < made; ++i) free(grid[i]);
    free(grid);
    return status;
}

Si l’allocation de la deuxième ligne échoue, made vaut 1 : seule la ligne zéro est donc libérée. Aucun pointeur de ligne non initialisé n’est lu. Une autre méthode utile consiste à initialiser des pointeurs indépendants à NULL et à diriger chaque échec vers une section de nettoyage unique ; free(NULL) rend cette section sûre après une réussite partielle.

Compte les durées de responsabilité, pas les appels d’allocation. Un tampon créé par malloc et redimensionné avec succès deux fois nécessite un seul free final appliqué au dernier résultat ; un appel réussi à realloc met déjà fin à la durée de vie du bloc précédent. L’allocateur peut conserver l’espace libéré pour de futures allocations : un moniteur système n’affiche donc pas nécessairement une baisse immédiate de la mémoire du processus.

Points clés

  • Le responsable libère chaque bloc après sa dernière utilisation.
  • Seule l’adresse de début d’une allocation encore active, ou NULL, est un argument valide pour free.
  • Réinitialiser un pointeur ne corrige pas les alias.
  • Libère les blocs intérieurs avant le bloc extérieur qui contient leurs adresses.
  • Les chemins d’erreur doivent libérer tous les blocs obtenus auparavant.
  • Après un redimensionnement réussi, le dernier bloc devra être libéré une fois avec free.
3

Redimensionner des tampons avec realloc

La taille finale des données d’entrée n’est pas toujours connue à l’avance. Un tampon redimensionnable permet à un programme de réserver davantage d’espace à mesure qu’il découvre ses besoins. Un agrandissement sûr préserve à la fois les valeurs existantes et la possibilité de réagir à un échec. Conserver ensemble un pointeur, une longueur et une capacité rend ces règles concrètes, tandis qu’une croissance géométrique limite les copies répétées.

3.1 Redimensionner sans perdre l’ancien bloc

realloc modifie la taille d’un bloc alloué. Elle peut agrandir ou réduire l’espace de stockage, et l’adresse renvoyée peut changer dans les deux cas. Inclus <stdlib.h> et traite la réussite et l’échec comme des événements distincts pour la durée de vie du bloc.

Ce qui est conservé et ce qui change

Lorsqu’un bloc grandit, son contenu existant est conservé jusqu’à l’ancienne taille. Les octets ajoutés ne sont pas initialisés. Lorsqu’il est réduit, seule la partie initiale qui tient dans la nouvelle taille est conservée. Un tableau contenant 3, 1, 4 et 1 conserve ces quatre valeurs lorsqu’il est agrandi à six entiers de type int ; les deux derniers éléments doivent recevoir une valeur avant utilisation. Avec des entiers de type int de 4 octets, la taille demandée passe de 16 à 24 octets.

L’allocateur peut agrandir le bloc sur place ou en allouer un autre ailleurs, copier les octets conservés et libérer l’ancien bloc. Ton code doit fonctionner dans les deux cas.

Recevoir le résultat dans un pointeur temporaire
int *tmp = realloc(nums, 6 * sizeof *nums);
if (tmp == NULL) {
    /* nums still owns its original block. */
} else {
    nums = tmp;
    nums[4] = 5;
    nums[5] = 9;
}

Pour cette demande de taille positive, un échec laisse le bloc d’origine alloué et inchangé. Le programme peut continuer à l’utiliser ou le libérer avant de signaler l’échec. En cas de réussite, la responsabilité porte désormais sur le bloc renvoyé. Affecte le nouveau résultat avant d’accéder au tableau redimensionné.

L’affectation directe nums = realloc(nums, bytes); fait perdre cette protection. Si l’appel échoue, elle écrase la seule adresse conservée par NULL. L’ancienne allocation reste réservée, mais le programme ne peut plus la libérer.

Recalculer les pointeurs enregistrés vers des éléments

Supposons que le code enregistre int *selected = &nums[2];. Un redimensionnement réussi met fin à la durée de vie de l’ancien objet, même si l’adresse renvoyée se trouve être identique. selected est invalide après cette réussite. Conserve l’indice 2 pendant le redimensionnement, puis recalcule selected = &nums[2]; à partir du nouveau pointeur si cet indice existe encore.

Ne compare pas l’ancien pointeur au résultat pour décider si les alias doivent être mis à jour. À chaque réussite, repars du nouveau résultat. Si la réduction a supprimé l’élément sélectionné, il n’y a aucun pointeur vers cet élément à recalculer.

Exemple complet d’agrandissement et de libération
#include <stdlib.h>

int main(void) {
    int *nums = malloc(4 * sizeof *nums);
    if (nums == NULL) return EXIT_FAILURE;
    for (size_t i = 0; i < 4; ++i) nums[i] = (int)i;

    int *tmp = realloc(nums, 6 * sizeof *nums);
    if (tmp == NULL) {
        free(nums);
        return EXIT_FAILURE;
    }
    nums = tmp;
    nums[4] = 4;
    nums[5] = 5;
    free(nums);
    return EXIT_SUCCESS;
}

Pour un nombre variable d’éléments, vérifie avant cet appel la multiplication qui donne le nombre d’octets, comme pour malloc. Demande des octets, pas des éléments : realloc(nums, 6) demande six octets.

realloc(NULL, positive_size) se comporte comme malloc, ce qui permet à un tampon vide de commencer avec un pointeur nul. Pour libérer un tampon, appelle explicitement free. Évite les redimensionnements à une taille nulle : le comportement des bibliothèques a varié par le passé, et le redimensionnement d’un bloc existant à zéro est un comportement indéfini en C23. Les tableaux locaux et les blocs déjà libérés ne sont pas des cibles valides pour un redimensionnement.

Points clés

  • Utilise un pointeur temporaire pour qu’un échec de redimensionnement à une taille positive préserve l’adresse détenue par le responsable du bloc.
  • L’agrandissement conserve l’ancien contenu, mais laisse les octets ajoutés non initialisés.
  • La réduction ne conserve que la partie qui tient encore dans le bloc.
  • Après une réussite, utilise le pointeur renvoyé et recalcule les pointeurs enregistrés vers des éléments.
  • Utilise free pour libérer l’espace de stockage ; traite explicitement les tailles nulles.

3.2 Un tableau qui grandit avec les données d’entrée

Un tableau extensible nécessite trois éléments d’état : son pointeur, sa longueur et sa capacité. La longueur compte les éléments initialisés et utilisés. La capacité compte les emplacements alloués pour les éléments. Maintiens la longueur inférieure ou égale à la capacité.

Ajouter dans un ordre sûr

Commence avec int *data = NULL; size_t len = 0, cap = 0;. Avant un ajout, vérifie si len == cap. Un tableau plein doit être agrandi avant l’écriture dans data[len]. Le pointeur et la capacité ne peuvent changer qu’après la réussite du redimensionnement. Stocke la nouvelle valeur, puis augmente la longueur.

L’exemple complet suivant ajoute dix valeurs avec une croissance géométrique :

#include <stdint.h>
#include <stdlib.h>

int main(void) {
    int *data = NULL;
    size_t len = 0, cap = 0;
    for (int value = 0; value < 10; ++value) {
        if (len == cap) {
            size_t next;
            if (cap == 0) {
                next = 4;
            } else {
                if (cap > SIZE_MAX / 2 / sizeof *data) {
                    free(data);
                    return EXIT_FAILURE;
                }
                next = cap * 2;
            }
            int *tmp = realloc(data, next * sizeof *data);
            if (tmp == NULL) {
                free(data);
                return EXIT_FAILURE;
            }
            data = tmp;
            cap = next;
        }
        data[len++] = value;
    }
    free(data);
    return EXIT_SUCCESS;
}

La vérification du débordement contrôle à la fois le doublement et la conversion en octets avant que l’un des deux produits puisse reboucler. La capacité initiale de quatre est elle aussi représentable pour ce type d’élément. En cas d’échec, le programme libère l’ancienne allocation dont il est toujours responsable et s’arrête ; il ne met pas la capacité à jour à tort.

Suivre séparément la longueur et la capacité

En partant d’une capacité de 4, les quatre premiers ajouts utilisent l’espace existant. Avant le cinquième, la capacité double et passe à 8. Avant le neuvième, elle double et passe à 16. Après dix ajouts, la longueur est de 10 et la capacité de 16. Si les deux agrandissements déplacent le bloc, ils copient au plus quatre et huit éléments, soit douze au total.

La capacité disponible est volontaire. Augmenter la capacité d’un élément à chaque ajout entraîne des copies répétées. En partant d’une capacité de 1, atteindre 1 000 éléments peut nécessiter 999 redimensionnements et 499 500 copies d’éléments. En doublant la capacité, on atteint 1 024 après dix redimensionnements et au plus 1 023 copies. Un redimensionnement particulier peut être coûteux, mais avec une croissance géométrique, le travail moyen par ajout reste constant.

Un facteur comme 1,5 laisse moins d’espace libre, mais entraîne des agrandissements plus fréquents. Si toutes les données ont été reçues, le programme peut réduire le bloc à la longueur utilisée en traitant l’échec avec la même méthode du pointeur temporaire. Un tableau vide est libéré avec free, et non par un redimensionnement à une taille nulle.

Les tampons de caractères nécessitent un emplacement supplémentaire

Pour une chaîne extensible, réserve de l’espace pour '\0'. Avant d’ajouter un caractère, agrandis le tampon si len + 1 >= cap. Avec une capacité de 8, sept caractères tiennent tout en laissant l’emplacement du caractère de fin ; « hello world » déclenche un agrandissement à 16 et nécessite douze octets, caractère de fin compris.

Un pointeur enregistré vers un élément devient invalide après un agrandissement réussi. Conserve un indice et retrouve l’élément à partir de la nouvelle adresse de début. Pour réinitialiser un tableau en vue d’une utilisation ultérieure, libère son bloc et exécute data = NULL; len = cap = 0;.

Points clés

  • La longueur compte les éléments utilisés ; la capacité compte les emplacements disponibles.
  • Agrandis le tableau avant un ajout lorsque la longueur est égale à la capacité.
  • Ne mets à jour le pointeur et la capacité qu’après un redimensionnement réussi.
  • La croissance géométrique limite les copies répétées au prix d’un espace libre supplémentaire.
  • Protège les calculs de croissance contre le débordement avant la multiplication.
  • Réserve un octet supplémentaire pour le caractère nul de fin d’une chaîne.
  • Conserve les indices pendant l’agrandissement et recalcule ensuite les pointeurs vers les éléments.
4

Reconnaître et prévenir les erreurs de mémoire

Les erreurs de mémoire proviennent du non-respect des règles de propriété, de durée de vie, de bornes ou d’initialisation. Chacune laisse une trace différente : une adresse perdue, un accès après libération, un indice hors du bloc ou une valeur lue trop tôt. Certaines erreurs provoquent immédiatement un plantage, tandis que d’autres corrompent des données et se manifestent plus tard. Lire le code en suivant les blocs et leur histoire aide à trouver la cause derrière le symptôme.

4.1 Trouver le chemin qui entraîne une fuite de mémoire

Une fuite de mémoire est une zone de stockage allouée dont un programme n’a plus besoin mais qu’il ne libère jamais. Souvent, le dernier pointeur disparaît, ce qui rend impossible un free ultérieur. Une fuite peut rester silencieuse pendant que l’utilisation de la mémoire augmente.

Suivre les adresses, pas les noms de variables
char *p = malloc(100);
if (p == NULL) return EXIT_FAILURE;
p = malloc(200);  /* loses the only address of the first block */
if (p == NULL) return EXIT_FAILURE;
free(p);

Le free final ne libère que le second bloc. Le nom p a été réutilisé, mais pas la première allocation. Si la seconde demande échoue, le bloc de 100 octets est tout de même perdu. Libère le premier bloc avant de remplacer son adresse lorsqu’il n’est plus nécessaire, ou conserve son adresse dans une autre variable responsable de sa libération.

Affecter NULL au pointeur responsable d’un bloc avant de le libérer entraîne la même perte. L’affectation modifie une variable pointeur ; elle ne demande pas à l’allocateur de libérer quoi que ce soit. Les variables locales automatiques disparaissent à la fin d’un appel, mais les blocs alloués vers lesquels elles pointaient subsistent.

Examiner chaque sortie d’une fonction

Un retour anticipé peut sauter le nettoyage :

char *line = malloc(100);
if (line == NULL) return EXIT_FAILURE;
if (input_is_bad) {
    free(line);
    return EXIT_FAILURE;
}
/* Use line on the successful path. */
free(line);

Les deux chemins libèrent le bloc. Dans une fonction qui possède davantage de ressources, une section de nettoyage unique réduit le nombre de chemins à examiner. Une fonction qui renvoie un résultat alloué transfère la responsabilité de sa libération ; une fonction qui abandonne simplement un pointeur local responsable d’un bloc provoque une fuite.

Affecter directement le résultat d’un appel à realloc qui échoue est une autre façon de perdre un bloc. Les allocations imbriquées fuient lorsque leur tableau extérieur de pointeurs est libéré avant que les adresses intérieures aient servi au nettoyage.

La répétition rend les petites fuites coûteuses

Supposons qu’une boucle alloue 100 octets à chaque passage sans jamais les libérer. Après 1 000 passages, elle a abandonné 100 000 octets. Libère chaque tampon à la fin de son passage ou alloue-le une fois avant la boucle, réutilise le bloc et libère-le ensuite. La réutilisation évite aussi le travail répété de l’allocateur lorsque la même capacité suffit.

Un service qui perd 64 octets par requête perd 640 000 octets après 10 000 requêtes, soit exactement 625 KiB. Après un million de requêtes, il a perdu 64 000 000 octets, soit environ 61 MiB. Les programmes de longue durée peuvent finir par ne plus pouvoir allouer de mémoire ou ralentir le système. Le système d’exploitation récupère normalement la mémoire du processus à sa fin, mais cela n’aide pas le service pendant son exécution.

Blocs perdus et encore accessibles : des rapports différents

Si un pointeur global contient encore l’adresse de début d’une allocation à la fin du programme, Valgrind qualifie le bloc d’« encore accessible ». L’adresse n’a pas été perdue ; le programme n’a simplement pas libéré le bloc. « Définitivement perdu » signifie qu’aucun pointeur utilisable ne subsiste. Organise le nettoyage de manière explicite pour que ces rapports fassent clairement apparaître les allocations inexpliquées.

Points clés

  • Écraser ou mettre à zéro le dernier pointeur responsable d’un bloc peut provoquer une fuite.
  • Les chemins normaux, les retours anticipés et les chemins d’échec nécessitent tous un nettoyage.
  • Les petites fuites répétées s’accumulent pendant l’exécution du programme.
  • Réutiliser un tampon adapté peut éviter à la fois des allocations répétées et des fuites à chaque itération.

4.2 Lorsqu’un pointeur survit à sa cible

Un pointeur devient pendant lorsque la durée de vie de sa cible prend fin. La variable peut encore exister, mais lire ou écrire l’ancien objet à travers ce pointeur est invalide. Renvoyer une adresse locale et libérer un bloc alloué peuvent tous deux créer ce problème.

Trouver la dernière utilisation avant la libération
int *scores = malloc(3 * sizeof *scores);
if (scores == NULL) return EXIT_FAILURE;
scores[0] = 90;
scores[1] = 75;
scores[2] = 82;
free(scores);
printf("%d\n", scores[0]);  /* use-after-free */

L’affectation à scores[0] a eu lieu pendant la durée de vie du bloc. Ce n’est pas le cas de la dernière lecture. Déplace free(scores) après l’affichage, une fois toutes les utilisations terminées. Affecter simplement scores = NULL avant l’affichage transforme l’accès invalide en déréférencement d’un pointeur nul ; cela ne fournit pas les données manquantes.

Il s’agit d’un comportement indéfini : le C n’impose aucune exigence sur le résultat. Le programme peut afficher une ancienne valeur, planter ou endommager silencieusement des données. Les octets libérés restent souvent en place jusqu’à ce que l’allocateur les réutilise. Un résultat plausible ne peut donc pas valider une lecture après libération.

Les alias partagent le problème de durée de vie
int *a = malloc(3 * sizeof *a);
if (a == NULL) return EXIT_FAILURE;
int *b = a;
free(a);
a = NULL;
b[0] = 1;  /* b also lost its live target */

Il y avait un seul bloc, dont l’adresse était contenue dans deux variables pointeurs. Le libérer à travers a met fin à sa durée de vie pour les deux. Rendre a nul ne modifie que a ; cela ne peut pas mettre b à jour. Le code qui utilise des alias empruntés doit terminer son travail avant que le responsable libère le bloc, et ces alias ne doivent plus être utilisés ensuite.

La double libération utilise elle aussi une durée de vie terminée

Appeler free deux fois pour un même bloc constitue une double libération, y compris lorsque le second appel passe par un alias. Cela peut corrompre les informations de gestion de l’allocateur ou faire échouer une allocation ultérieure. Certaines bibliothèques la détectent immédiatement et arrêtent le programme, mais cette détection immédiate n’est pas garantie.

Affecter NULL à la variable responsable après la libération aide en cas d’appel répété au nettoyage, car free(NULL) est valide. Cela ne rend pas free(b) sûr lorsque b désigne encore l’ancien bloc. Un seul responsable doit décider du moment où le bloc est libéré.

Le redimensionnement met lui aussi fin à l’ancienne durée de vie

Un appel réussi à realloc remplace l’ancien objet même si la valeur numérique de l’adresse renvoyée est inchangée. Les pointeurs enregistrés vers l’intérieur de l’ancien bloc doivent être recalculés à partir du pointeur renvoyé. Stocke les positions sous forme d’indices tant qu’un agrandissement est possible. À l’inverse, un appel à realloc de taille positive qui échoue laisse l’ancienne allocation active.

Une fonction qui renvoie l’adresse d’une variable locale automatique crée une erreur de durée de vie similaire sans aucun appel à free : la cible cesse d’exister au retour de la fonction. Utilise un espace de stockage appartenant à la fonction appelante ou un résultat dans le tas dont le responsable est clairement désigné. L’utilisation après libération et la double libération peuvent aussi devenir des failles de sécurité lorsqu’un attaquant peut influer sur le contenu de l’espace réutilisé.

Points clés

  • Un pointeur pendant vise un objet dont la durée de vie est terminée.
  • Termine toutes les lectures et écritures avant de libérer le bloc.
  • Une libération rend invalides tous les alias vers l’ancien objet.
  • Rendre nul le pointeur responsable facilite les nettoyages répétés, mais ne corrige pas les alias empruntés.
  • Une double libération peut endommager les informations de gestion de l’allocateur.
  • Un appel réussi à realloc rend invalides les pointeurs enregistrés vers l’intérieur de l’ancien bloc.

4.3 Bornes valides et valeurs initialisées

Un accès valide à un élément nécessite à la fois suffisamment d’espace et une valeur affectée avant sa lecture. Un bloc peut avoir la bonne taille mais contenir des données non initialisées, ou contenir des valeurs utiles mais être utilisé hors de ses bornes.

Vérifier la plage des indices

Pour n éléments, les indices valides vont de zéro à n - 1 inclus. Une boucle avec i <= n effectue un accès de trop. Avec dix entiers de type int de 4 octets, l’allocation est de 40 octets ; p[10] commence immédiatement après ces octets. Utilise i < n pour parcourir les dix éléments.

Un indice issu des données d’entrée doit aussi être vérifié avant utilisation. Une valeur signée saisie doit être positive ou nulle et inférieure au nombre d’éléments. Conserve le nombre avec le pointeur et passe les deux aux fonctions. Un pointeur seul ne peut pas décrire la plage autorisée.

Prévoir de la place pour le caractère de fin des chaînes
char *copy = malloc(strlen(s));
strcpy(copy, s);  /* writes the terminator beyond the allocation */

Ce fragment incorrect nécessite aussi une vérification de l’échec. Pour corriger la taille, utilise malloc(strlen(s) + 1), puis vérifie le résultat avant de copier. « Amsterdam » comporte neuf caractères mais nécessite dix octets avec '\0'. Le caractère de fin est un octet effectivement stocké, même s’il n’est pas affiché dans le texte.

Un tableau de pointeurs ne stocke pas les chaînes
char **names = malloc(n * sizeof *names);

Cette instruction réserve n emplacements de pointeurs. Elle ne crée aucun tampon de caractères vers lequel ces pointeurs peuvent pointer. Avant d’utiliser names[i] comme chaîne, affecte-lui une adresse de chaîne valide. Il peut emprunter un tableau existant ou un littéral dont la durée de vie est suffisante, ou être responsable d’une copie de chaîne allouée séparément. Seules les chaînes dont le tableau est responsable sont libérées lors du nettoyage des blocs imbriqués.

Le même raisonnement sur la taille s’applique aux tableaux numériques : malloc(n) compte des octets et sizeof p mesure un pointeur. Utilise n * sizeof *p, avec une vérification du débordement et du pointeur nul, pour exprimer correctement un nombre d’éléments.

Écrire avant de lire
int *t = malloc(3 * sizeof *t);
if (t == NULL) return EXIT_FAILURE;
t[0] = 5;
t[1] = 7;
printf("%d\n", t[0] + t[1] + t[2]);  /* t[2] was never set */
free(t);

t[2] se trouve dans le bloc alloué, mais sa valeur n’est pas initialisée. Affecte-lui une valeur avant la somme ou utilise calloc lorsque zéro est la valeur initiale voulue. Les octets ajoutés lors d’un agrandissement avec realloc demandent la même attention. Être dans les bornes n’initialise pas la mémoire.

Pourquoi le plantage peut se produire plus tard

Une écriture hors limites peut endommager les données voisines ou les informations de gestion de l’allocateur. Une lecture hors limites peut exposer des données sans rapport. Les deux provoquent un comportement indéfini. Un petit débordement peut toucher de la mémoire mappée et échapper à une détection immédiate ; un free ultérieur peut être le premier échec visible.

Vérifie séparément la taille de l’allocation, la plage des indices et l’historique d’initialisation. Une allocation plus grande ne peut pas fournir une valeur qui a un sens à un élément non initialisé, et la mise à zéro ne peut pas rendre valide un indice hors limites.

Points clés

  • Pour n éléments, les indices valides vont de zéro à n - 1 inclus.
  • Le stockage d’une chaîne comprend un octet pour le caractère nul de fin.
  • Un tableau de pointeurs vers des chaînes n’alloue pas les chaînes elles-mêmes.
  • Initialise toutes les valeurs lues par un calcul, y compris les éléments nouvellement ajoutés.
  • Une écriture invalide peut corrompre des données bien avant un plantage visible.

4.4 Remonter d’un plantage à sa cause

Une erreur de segmentation est une réponse du système d’exploitation à un accès mémoire interdit. Ses causes fréquentes comprennent le déréférencement d’un pointeur nul ou non initialisé, l’écriture dans un littéral de chaîne, un accès très éloigné des bornes d’un tableau, l’utilisation d’un espace libéré et l’épuisement de la pile.

Ce que le message indique

Sous Linux, SIGSEGV arrête généralement le processus et un interpréteur de commandes peut afficher « Segmentation fault (core dumped) ». Un code de retour courant est 139, selon la convention qui additionne 128 et le numéro 11 du signal. macOS peut signaler SIGSEGV ou SIGBUS pour des accès invalides. Windows signale l’erreur comparable comme une violation d’accès, souvent avec le code d’exception 0xC0000005.

Ces messages indiquent un accès qui a échoué, pas toute son histoire. Un petit débordement peut corrompre un pointeur tôt dans l’exécution et provoquer un plantage bien plus tard. De nombreux accès invalides restent dans une mémoire que le processus peut adresser : ils corrompent alors silencieusement des données sans déclencher d’erreur.

Commencer par les informations de ligne

Compile un exécutable de débogage avec les avertissements :

cc -std=c17 -g -O0 -Wall -Wextra program.c -o program

Exécute-le ensuite dans un débogueur ou un outil de vérification de la mémoire. Une trace de pile liste les appels de fonctions actifs au moment du plantage. Trouve le cadre le plus haut qui appartient à ton propre code. Des fonctions de bibliothèque comme free peuvent être l’endroit où un état incorrect devient visible, alors qu’un appel antérieur dans ton programme l’a créé.

À la ligne signalée, identifie chaque pointeur déréférencé. Pour chacun, remonte de sa valeur à son origine. L’allocation a-t-elle été vérifiée ? Sa cible existe-t-elle encore ? L’indice est-il dans les bornes du bloc ? Une adresse valide a-t-elle été affectée au pointeur ?

Examiner un résultat nul
int *p = NULL;
printf("%d\n", *p);  /* invalid dereference */

L’affichage nécessite une lecture à travers p, mais aucun objet entier n’existe à cette adresse. Si p provient d’une fonction d’allocation, vérifie le résultat avant cette ligne et suis un chemin d’erreur en cas d’échec. S’il a été mis à zéro après la libération, déplace l’utilisation nécessaire avant la libération ou revois l’endroit où les données sont conservées.

Un pointeur non initialisé, comme dans int *p; *p = 5;, n’a reçu aucune cible valide. Une allocation, l’adresse d’un objet encore actif ou un pointeur emprunté vérifié doit établir cette cible avant le déréférencement.

Suivre la cause lorsque les symptômes changent

Une fonction récursive sans fin peut épuiser les cadres de pile dans une version non optimisée. Une condition d’arrêt corrige la logique. Un compilateur optimisant peut toutefois transformer certains appels récursifs en boucle : l’absence de plantage de la pile ne rend donc pas utile ce calcul sans limite.

Les messages de progression affichés ne sont pas non plus une preuve parfaite. stdout est mis en tampon : une sortie peut ne pas être écrite lorsqu’un processus plante, surtout si elle est redirigée. Pour un message de diagnostic, utilise stderr ou appelle fflush(stdout) après un affichage de progression. Ajouter un affichage peut modifier la disposition de la mémoire et masquer une erreur ; cela n’a pas corrigé l’accès invalide. Relance le programme avec un outil de vérification pour trouver l’opération fautive elle-même.

Points clés

  • Une erreur de segmentation signale un accès interdit ; la corruption initiale peut être antérieure.
  • Compile avec les informations de débogage et repère le premier cadre pertinent dans ton propre code.
  • Remonte à l’origine de chaque pointeur, à la durée de vie de sa cible et aux bornes des indices.
  • La mise en tampon des sorties peut masquer un affichage exécuté avant un plantage.
  • Un symptôme qui change après l’ajout d’un affichage doit toujours être examiné.
5

Repérer les erreurs de mémoire avec Valgrind et les sanitizers

Les outils de vérification de la mémoire rendent visibles les erreurs cachées en signalant l’accès et l’histoire du bloc. Valgrind observe un exécutable de débogage ; AddressSanitizer ajoute des vérifications lors de sa compilation. Apprends à relier leurs messages aux bornes des éléments, aux points d’allocation et aux points de libération, puis à effectuer une correction qui respecte la propriété des blocs. Après une nouvelle exécution sans erreur, utilise d’autres données d’entrée pour parcourir d’autres chemins.

5.1 Lire un rapport Valgrind

L’outil par défaut de Valgrind, Memcheck, observe un programme compilé pendant son exécution. Il suit les bornes du tas, les durées de vie des allocations et l’initialisation des valeurs. Il ne nécessite pas de compilation avec un sanitizer, mais les informations de débogage rendent ses rapports bien plus faciles à exploiter.

Compiler et exécuter
cc -std=c17 -g -O0 -Wall -Wextra program.c -o program
valgrind --leak-check=full ./program

Place les arguments du programme après ./program. Ajoute --show-leak-kinds=all pour lister toutes les catégories de fuites et --track-origins=yes lorsque l’origine d’une valeur non initialisée n’est pas claire.

Valgrind fonctionne sous Linux et sur certains autres systèmes de type Unix, mais pas nativement sous Windows ni sur les Mac équipés d’une puce Apple. Memcheck ralentit souvent l’exécution d’environ 20 à 30 fois. Utilise d’abord une petite donnée d’entrée qui reproduit l’erreur, puis effectue d’autres tests. Un test sans erreur ne couvre que les chemins effectivement exécutés.

Lire séparément la taille de l’accès et celle du bloc

Un rapport commençant par « Invalid write of size 4 » décrit une écriture de quatre octets. Une adresse située à « 0 bytes after a block of size 40 alloc'd » commence immédiatement après une allocation de 40 octets. Si ce bloc contient dix entiers de type int de 4 octets, l’accès invalide commence à l’élément d’indice 10 ; le dernier élément valide a pour indice 9.

Le rapport indique aussi la ligne de l’accès et la pile d’appels de l’allocation. Utilise-les ensemble : l’allocation indique les bornes prévues et l’accès indique l’indice qui les a dépassées. « Size 4 » ne signifie pas que le programme a dépassé les bornes de quatre éléments.

« Invalid read of size N » décrit de même une lecture invalide. Une adresse à l’intérieur d’un bloc décrit comme « free'd » signale un problème de durée de vie ; compare les emplacements de son allocation, de sa libération et de l’accès. « Invalid free() / delete / delete[] / realloc() » peut indiquer une libération en double ou une adresse qui n’a jamais été le début d’une allocation valide.

Pour une condition utilisant des données non initialisées, Memcheck peut afficher « Conditional jump or move depends on uninitialised value(s) ». Cette orthographe appartient au message de l’outil. La condition signalée a utilisé la valeur ; --track-origins=yes aide à trouver l’origine de son stockage non initialisé.

Lire la catégorie de fuite
Catégorie du rapportSignification
Définitivement perduAucun pointeur vers le bloc ne subsiste
Indirectement perduLe bloc n’est accessible qu’à travers un autre bloc perdu
Potentiellement perduSeul un pointeur intérieur a été trouvé
Encore accessibleUn pointeur subsiste à la fin, mais le bloc n’a pas été libéré

Supposons qu’une fonction auxiliaire alloue dix entiers de type int de 4 octets dans un pointeur local et se termine sans libérer le bloc ni renvoyer son adresse. Son bloc de 40 octets est définitivement perdu. Un pointeur global conservant l’adresse de début rendrait au contraire un bloc non libéré encore accessible. Les totaux d’allocations et de libérations du bilan du tas décrivent des événements, notamment les redimensionnements ; ils ne remplacent pas le raisonnement sur la propriété.

Une exécution sans erreur peut afficher « All heap blocks were freed -- no leaks are possible » et « ERROR SUMMARY: 0 errors from 0 contexts ». Memcheck ne détecte généralement pas tous les dépassements de tableaux locaux ou globaux qui restent dans une mémoire par ailleurs valide. Corrige la première erreur signalée, relance le programme et teste d’autres données d’entrée, notamment une entrée vide et des chemins avec retour anticipé.

Points clés

  • Utilise les informations de débogage pour que Memcheck puisse identifier les lignes de ton code source.
  • Lis ensemble la taille de l’accès, les bornes du bloc et l’historique de l’allocation.
  • Utilise --track-origins=yes pour retrouver l’origine des valeurs non initialisées.
  • Distingue les blocs perdus des blocs encore accessibles à la fin.
  • Corrige la première erreur avant d’interpréter ses conséquences ultérieures.
  • Utilise plusieurs données d’entrée, car un outil de vérification n’observe que les chemins exécutés.

5.2 Intégrer des vérifications au programme

AddressSanitizer, ou ASan, ajoute des vérifications des accès à la mémoire pendant la compilation. L’exécutable obtenu se vérifie lui-même pendant son exécution. Il est utile pour les tests réguliers de développement, car son exécution est généralement environ deux fois plus lente, un ralentissement bien inférieur à celui de Memcheck.

Compiler et effectuer l’édition de liens avec le sanitizer
cc -std=c17 -g -fsanitize=address -fno-omit-frame-pointer \
    program.c -o program-asan
./program-asan

Utilise l’option du sanitizer à la compilation et à l’édition de liens finale lorsque ces étapes sont séparées. -g fournit les informations de ligne et -fno-omit-frame-pointer aide à conserver des piles d’appels utiles. Le compilateur de Microsoft utilise /fsanitize=address.

ASan détecte les débordements dans le tas, dans la pile et dans les tableaux globaux, les utilisations après libération, les libérations invalides et les doubles libérations. Les vérifications de durée de vie liée à la portée et, facultativement, des utilisations après retour de fonction peuvent aussi détecter les pointeurs vers d’anciennes variables locales. Il nécessite une recompilation et de la mémoire supplémentaire. Garde cette instrumentation dans les versions de développement et de test.

Lire les trois parties de l’historique

Un rapport d’utilisation après libération peut commencer par « ERROR: AddressSanitizer: heap-use-after-free ». La mention suivante « READ of size 4 » décrit l’opération invalide. Le rapport indique ensuite où la mémoire a été libérée avec « freed by thread T0 here: » et où elle a été allouée avec « previously allocated by thread T0 here: ».

Commence par la première ligne pertinente de ton code dans chaque pile d’appels. L’allocation crée le bloc ; la libération met fin à sa durée de vie ; la lecture signalée a lieu après cette fin. Décide s’il faut déplacer la libération après la lecture ou cesser d’accéder au bloc après sa libération. Le rapport fournit l’historique, tandis que la propriété prévue détermine la correction.

Pour « heap-buffer-overflow », une adresse « located 0 bytes after 20-byte region » commence juste après la région. Ne convertis les octets en éléments qu’après avoir vérifié la taille du type utilisé par le programme. ASan s’arrête normalement à la première erreur : la corriger peut donc en révéler une autre à l’exécution suivante.

La recherche de fuites et celle de valeurs non initialisées sont distinctes

LeakSanitizer recherche les allocations non libérées à la fin du programme. Il est normalement activé avec ASan sous Linux. Sur les chaînes d’outils macOS qui le prennent en charge, il peut être activé avec ASAN_OPTIONS=detect_leaks=1 ; certaines chaînes d’outils Mac ne le proposent pas. L’outil distinct leaks d’Apple est une autre solution pour rechercher les fuites sur ces systèmes.

ASan ne suit pas l’initialisation d’une valeur située dans les bornes. Pour ce problème, utilise Memcheck ou MemorySanitizer de Clang sur une configuration Linux prise en charge. UndefinedBehaviorSanitizer vérifie d’autres types de comportements indéfinis, comme le débordement d’entier signé, et peut être associé à ASan avec -fsanitize=address,undefined.

Prévoir une exécution distincte pour chaque outil
BesoinOutil utile
Vérifications rapides pendant le développement, notamment des bornes des tableaux locauxVersion compilée avec ASan
Suivi des valeurs non initialisées sans instrumentation par un sanitizerMemcheck sur un système pris en charge
Fuites d’allocations à la fin du programmeLeakSanitizer ou Memcheck

N’exécute pas un exécutable ASan sous Valgrind. Compile un exécutable de débogage ordinaire distinct pour Memcheck et n’associe pas ASan et MemorySanitizer dans une même compilation. Après une exécution sans erreur, varie les données d’entrée pour exécuter d’autres chemins d’allocation, d’agrandissement et de nettoyage.

Points clés

  • ASan nécessite une compilation et une édition de liens avec son option d’instrumentation.
  • Lis les piles d’appels de l’accès invalide, de la libération et de l’allocation pour reconstituer une erreur de durée de vie.
  • LeakSanitizer recherche les fuites à la fin du programme lorsque la plateforme le prend en charge.
  • ASan ne détecte pas les lectures ordinaires de valeurs non initialisées.
  • Utilise des exécutions distinctes et des versions adaptées pour ASan et Memcheck.

5.3 Transformer un rapport en correction vérifiée

Un rapport de mémoire indique une erreur observée et une partie de son histoire. Une correction utile rétablit une taille, des bornes, une initialisation et une propriété correctes, puis vérifie à nouveau le programme corrigé.

Réduire et reproduire

Trouve les plus petites données d’entrée qui produisent encore l’erreur. Compile avec -g, les avertissements et peu d’optimisation, puis utilise Memcheck ou une version compilée avec ASan. Lis le premier rapport avant d’examiner les suivants ; un état corrompu peut produire une série d’échecs trompeurs.

Supposons qu’un programme contienne :

size_t n = 5;
int *v = malloc(n * sizeof *v);
for (size_t i = 0; i <= n; ++i) v[i] = (int)(i * i);
printf("%d\n", v[2]);
return 0;

Sur un système où les entiers de type int occupent 4 octets, ASan signale une écriture de quatre octets au premier octet situé après une allocation de 20 octets. L’écriture se trouve dans la boucle. Memcheck peut aussi signaler le débordement, puis une allocation non libérée ; le classement du bloc comme perdu ou encore accessible dépend des pointeurs qui subsistent à la fin.

Relier le rapport au code

Cinq entiers de type int occupent les indices de zéro à quatre. La condition i <= n atteint l’indice cinq. La taille de l’allocation est correcte, mais pas la borne de la boucle. Agrandir le bloc masquerait le symptôme tout en laissant le parcours incorrect.

Il manque aussi une vérification de l’allocation et le bloc dont le programme est responsable n’est pas libéré. Ces problèmes nécessitent leurs propres corrections, même si le premier rapport a arrêté le programme avant qu’il les atteigne.

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

int main(void) {
    size_t n = 5;
    int *v = malloc(n * sizeof *v);
    if (v == NULL) return EXIT_FAILURE;
    for (size_t i = 0; i < n; ++i) v[i] = (int)(i * i);
    printf("%d\n", v[2]);
    free(v);
    return EXIT_SUCCESS;
}

Le programme corrigé initialise cinq éléments valides, affiche 4 pendant que le bloc existe et le libère. Ce petit nombre fixe est représentable ; pour un nombre issu des données d’entrée, il faut aussi vérifier le débordement du calcul de taille avant l’allocation.

Utiliser l’historique du rapport pour choisir la correction adaptée

Pour une utilisation après libération, compare les piles d’appels de la libération et de l’accès afin de trouver la dernière utilisation légitime. Déplace la libération après celle-ci ou supprime l’accès devenu invalide. Affecter NULL à un pointeur ne peut pas rendre à nouveau disponibles les données qu’il désignait.

Pour une fuite, la pile d’appels de l’allocation indique où le bloc a été créé. Elle ne désigne pas la bonne ligne de libération. Suis la propriété à travers les retours réussis et les échecs pour trouver où se termine la dernière utilisation. Pour une double libération, supprime la libération en double ou corrige les responsabilités contradictoires. Supprimer la seule libération valide crée une fuite.

Pour les données non initialisées, ajoute --track-origins=yes à Memcheck. La ligne qui utilise les données peut être éloignée de l’allocation qui a fourni les octets non initialisés. Initialise chaque valeur lue par le calcul ou utilise calloc lorsque zéro est la bonne valeur initiale.

Vérifier la correction sur plusieurs chemins

Recompile et relance le programme avec les données d’entrée qui reproduisaient initialement l’erreur. Continue jusqu’à obtenir un rapport sans erreur, puis essaie une entrée vide, les limites d’agrandissement, des données ordinaires et des retours d’erreur. Les avertissements du compilateur détectent certains problèmes avant l’exécution ; les outils d’exécution vérifient les chemins atteints par ces tests.

Points clés

  • Reproduis l’erreur avec une petite donnée d’entrée et lis la première erreur.
  • Utilise la taille du bloc et la position de l’accès pour identifier l’erreur de bornes réelle.
  • Vérifie les échecs d’allocation et le nettoyage même lorsque la première erreur d’accès arrête l’exécution.
  • Les rapports de fuite indiquent les points d’allocation ; la propriété détermine les points de libération.
  • Après la correction, recompile, relance le cas de reproduction et parcours d’autres chemins.

Gestion dynamique de la mémoire en C : exemples

5 Leçons Interactives

1

Suivre un objet au fil des appels de fonctions

9 étapes
Suivre les données locales, statiques et allouées, puis choisir le stockage d’un résultat renvoyé.
  1. 1
    IntroductionUne adresse nécessite un objet encore actif

    Un pointeur permet à une partie du programme de retrouver des données créées ailleurs. Son utilité dépend du fait que ces données existent encore ou non lorsqu’il est utilisé.

    Suis une fonction auxiliaire qui crée un tableau d’entiers, renvoie son adresse et laisse la fonction appelante utiliser le résultat. Pendant le suivi de l’appel, distingue le pointeur local du tableau : ils n’ont pas nécessairement la même durée de vie.

  2. 2
    ExplicationNommer les trois durées de vie

    Une variable locale ordinaire a une durée de stockage automatique et cesse d’exister à la fin de son bloc. Une variable globale ou une variable locale static existe pendant toute l’exécution du programme. Un bloc alloué existe jusqu’à sa libération.

    Sur les systèmes courants, les objets automatiques occupent la pile d’appels et les allocations utilisent le tas. Ces noms décrivent des implémentations courantes ; les règles du C définissent les durées de vie quelle que soit la disposition de la mémoire.

  3. 3
    ExempleCréer le pointeur local et le bloc

    Dans make_array, int *p = malloc(5 * sizeof *p); crée deux objets distincts : le pointeur local et, en cas de réussite, une allocation pour cinq entiers de type int.

    Là où un int occupe 4 octets, le bloc contient 20 octets. Vérifie p avant utilisation. La variable pointeur appartient à l’appel de fonction, mais le bloc alloué ne fait pas partie du stockage automatique de cet appel.

  4. 4
    ExempleRenvoyer l’adresse

    La fonction auxiliaire renvoie p et la fonction appelante stocke le résultat dans int *nums. L’adresse est copiée ; le tableau ne l’est pas. Les variables locales de la fonction auxiliaire cessent alors d’exister.

    En cas de réussite, nums désigne toujours le tableau alloué. La fonction appelante vérifie le résultat, initialise et utilise ses éléments, puis le libère. Le retour n’a pas libéré le bloc, car sa durée de stockage est allouée.

  5. 5
    DéfiQu’est-ce qui survit au retour ?

    Une fonction auxiliaire crée une variable automatique int count et un tableau distinct dans le tas. Elle renvoie l’adresse du tableau. Lequel de ces deux objets la fonction appelante peut-elle encore utiliser ?

    ---reveal---

    Le tableau du tas reste actif jusqu’à sa libération. La variable automatique count de la fonction auxiliaire a cessé d’exister au retour. L’adresse copiée ne donne accès qu’au tableau ; conserver une adresse n’aurait pas préservé count si la fonction auxiliaire avait renvoyé &count à la place.

  6. 6
    ExempleEssayer de renvoyer un tableau local

    Modifie maintenant la fonction auxiliaire pour qu’elle déclare int values[5]; et renvoie values. Ce tableau est automatique. Il cesse d’exister au retour de la fonction auxiliaire, laissant la fonction appelante sans cible encore active.

    La taille reste de cinq entiers de type int, mais la durée de vie est différente. Pour obtenir un résultat utilisable, on peut le placer dans un tampon fourni par la fonction appelante ou l’allouer et le lui transférer en précisant clairement la responsabilité du nettoyage.

  7. 7
    LienChoisir les règles d’utilisation d’un tampon

    Un tampon de résultat statique survit au retour, mais chaque appel le partage. Un appel ultérieur peut écraser un résultat précédent. Un résultat dans le tas peut donner à chaque appel son propre espace de stockage indépendant, dont la fonction appelante est responsable de la libération.

    Pour un travail temporaire, un petit tableau local de taille fixe est plus simple. Pour les tableaux dont la taille vient des données d’entrée, les très grands tableaux ou les tableaux extensibles, une allocation vérifiée permet au programme de gérer les échecs et de contrôler la durée de vie.

  8. 8
    DéfiDeux résultats doivent rester indépendants

    Une fonction appelante a besoin de deux résultats modifiables issus d’appels successifs et doit conserver les deux. Elle ne fournit aucun tampon. Un seul tableau de résultat statique répondrait-il à ce besoin ? Quelle solution convient ?

    ---reveal---

    Un seul tableau statique serait partagé et écrasé. Renvoie un résultat alloué distinct à chaque appel, vérifie chaque résultat et laisse la fonction appelante libérer les deux après leurs dernières utilisations. La durée de vie dans le tas assure leur survie ; des blocs distincts assurent leur indépendance.

  9. 9
    RécapitulatifDistinguer l’adresse de la durée de vie

    Suis l’objet visé par un pointeur : les variables locales automatiques cessent d’exister à la fin de leur bloc, les objets statiques existent pendant toute l’exécution et les blocs alloués doivent être libérés explicitement.

    Pour un résultat renvoyé, demande-toi si le stockage survit et si les fonctions appelantes ont besoin de copies indépendantes. Pour un travail temporaire, demande-toi si un petit tableau local de taille fixe suffit. La taille et la durée de vie nécessaires déterminent la solution avant même l’écriture d’un appel d’allocation.

2

Allouer une grille et nettoyer chaque chemin

13 étapes
Partir de la taille des éléments pour effectuer des demandes vérifiées, initialiser les valeurs et nettoyer après une réussite partielle.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionPrévoir la réussite et l’échec
  2. 2
    ExplicationConvertir les nombres d’éléments en octets
  3. 3
    ConseilVérifier avant de multiplier
  4. 4
    DéfiTaille de l’élément ou du pointeur ?
  5. 5
    ExplicationDonner une valeur initiale aux compteurs
  6. 6
    ExempleUtiliser la ligne mise à zéro
  7. 7
    DéfiEt en cas d’agrandissement ultérieur ?
  8. 8
    ExplicationCompter les blocs, pas les noms de pointeurs
  9. 9
    ExempleArrêter la construction en cas d’échec partiel
  10. 10
    DéfiChoisir l’ordre du nettoyage
  11. 11
    Point cléUn responsable peut avoir des emprunteurs
  12. 12
    DéfiUn chemin s’arrête prématurément
  13. 13
    RécapitulatifPrévoir le nettoyage avec la construction
3

Ajouter des valeurs sans perdre de données

12 étapes
Suivre la longueur, la capacité et les échecs de redimensionnement d’un tableau dynamique, y compris le cas du caractère de fin d’une chaîne.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionAgrandir avant le stockage suivant
  2. 2
    ExplicationUn redimensionnement peut remplacer l’objet
  3. 3
    ExempleDistinguer l’échec de la réussite
  4. 4
    DéfiQue peut-on conserver après une réduction ?
  5. 5
    ExplicationLa longueur et la capacité répondent à des questions différentes
  6. 6
    ExempleFranchir la première limite d’agrandissement
  7. 7
    ExempleFranchir la limite suivante
  8. 8
    Point cléL’espace libre réduit le nombre de copies
  9. 9
    DéfiQuel état reste après un ajout qui échoue ?
  10. 10
    LienUne chaîne réserve un emplacement pour son caractère de fin
  11. 11
    DéfiUne sélection empruntée lors d’un agrandissement
  12. 12
    RécapitulatifNe valider l’état qu’après une réussite
4

Examiner toute l’histoire d’un bloc

15 étapes
Distinguer les fuites, les alias invalides, les erreurs de bornes et les valeurs non initialisées, puis remonter d’un plantage à sa cause.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionQuatre questions pour chaque accès
  2. 2
    ExemplePerdre une allocation en remplaçant son adresse
  3. 3
    DéfiRendre le pointeur nul effectue-t-il le nettoyage ?
  4. 4
    ConseilExaminer les sorties autant que les boucles
  5. 5
    ExempleLibérer une fois rend tous les alias invalides
  6. 6
    DéfiCorriger la dernière utilisation
  7. 7
    Point cléUn code invalide peut produire le résultat attendu
  8. 8
    ExempleCompter le caractère nul de fin de chaîne
  9. 9
    ExempleAlloué ne signifie pas initialisé
  10. 10
    DéfiDistinguer deux bornes
  11. 11
    ExplicationUne erreur est un symptôme
  12. 12
    ExempleExaminer l’accès qui provoque le plantage
  13. 13
    DéfiUn affichage fait disparaître le plantage
  14. 14
    DéfiCombiner propriété et bornes
  15. 15
    RécapitulatifIdentifier la règle enfreinte
5

Lire un rapport et vérifier la correction

14 étapes
Choisir un outil de vérification, interpréter les octets et les piles d’appels, puis corriger un débordement et un nettoyage manquant.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionTransformer un message en historique de bloc
  2. 2
    ExplicationUtiliser une version de débogage ordinaire pour Memcheck
  3. 3
    ExempleInterpréter la description de l’adresse
  4. 4
    DéfiLire les octets comme des octets
  5. 5
    ConseilChoisir une option de rapport adaptée au problème
  6. 6
    ExplicationCompiler séparément un exécutable ASan
  7. 7
    ExempleSuivre une utilisation après libération dans trois piles d’appels
  8. 8
    DéfiUne exécution ASan sans erreur manque une erreur de valeur
  9. 9
    ExempleRelier le rapport à la boucle
  10. 10
    ExempleMaintenir le bloc actif pendant son utilisation
  11. 11
    DéfiOù faut-il corriger une fuite ?
  12. 12
    LienVérifier la fin du programme et la prise en charge par la plateforme
  13. 13
    DéfiTerminer le cycle de correction
  14. 14
    RécapitulatifUtiliser les indices, corriger et relancer

Gestion dynamique de la mémoire en C : QCM

50 Questions Pratiques en 5 séries

Série 1 : Durées de vie, nombres d’octets et premières vérifications10 Questions Pratiques

Question 1 sur 10

Un tampon contient six entiers de type int initialisés. Le code enregistre l’indice 4, réduit avec succès le bloc à trois entiers, puis cherche à retrouver l’élément précédemment sélectionné. Quelle est une action valide ?
  1. Recalculer le pointeur sélectionné sous la forme &data[4].
  2. Rejeter la sélection, car l’élément d’indice 4 a été supprimé.
  3. Lire l’ancien pointeur sélectionné avant une autre allocation.
  4. Lire l’élément d’indice 4 si l’adresse du bloc est inchangée.
Afficher la réponseMasquer la réponse
Réponse : B
La réduction ne conserve que les trois premiers éléments, d’indices zéro à deux. L’indice 4 ne désigne plus un élément du bloc redimensionné : recalculer un pointeur ne permet donc pas de le retrouver. La durée de vie de l’ancien objet a aussi pris fin lors de la réussite, même si l’adresse est restée identique ; les pointeurs enregistrés ne peuvent pas récupérer les données supprimées.

Question 2 sur 10

Une fonction doit produire un résultat texte modifiable qui survive à son retour. La fonction appelante ne fournit aucun tampon et les appels distincts doivent conserver des résultats indépendants. Quelle solution de stockage convient ?
  1. Renvoyer un tableau local ; la fonction appelante garde son adresse.
  2. Renvoyer un tampon statique ; la fonction appelante le libère.
  3. Renvoyer le littéral ; la fonction appelante modifie ses lettres.
  4. Renvoyer une copie allouée ; la fonction appelante la libère.
Afficher la réponseMasquer la réponse
Réponse : D
Une copie allouée a une durée de vie qui dépasse l’appel qui l’a créée et fournit un stockage indépendant pour chaque résultat. Un tampon statique survit aussi, mais partage un même résultat entre les appels et ne peut pas être libéré par la fonction appelante. Un tableau local cesse d’exister au retour et un littéral de chaîne ne fournit pas de stockage modifiable.

Question 3 sur 10

Un tableau nécessite 13 entiers de type int. Sur ce système, sizeof(int) vaut 4. Combien d’octets l’allocation doit-elle demander ?
  1. 52 octets
  2. 13 octets
  3. 17 octets
  4. 104 octets
Afficher la réponseMasquer la réponse
Réponse : A
La demande nécessite 13 éléments de 4 octets chacun, soit 52 octets. Une demande de 13 octets confond le nombre d’éléments et le nombre d’octets ; elle ne peut pas contenir treize entiers de type int sur ce système.

Question 4 sur 10

Après un appel réussi à int *hits = calloc(6, sizeof *hits);, que vaut hits[4] avant toute affectation ultérieure ?
  1. Une valeur non initialisée
  2. L’entier 6
  3. 0
  4. L’entier 4
Afficher la réponseMasquer la réponse
Réponse : C
calloc met chaque octet à zéro, ce qui donne la valeur zéro aux éléments entiers. L’indice 4 se trouve dans le tableau de six éléments : sa valeur peut donc être lue comme zéro après vérification de l’allocation. Un espace qui vient d’être alloué par malloc ne serait pas initialisé ; cette règle ne s’applique pas à cet appel réussi à calloc.

Question 5 sur 10

Une fonction alloue un tableau de pointeurs et quatre blocs de lignes distincts. Les cinq allocations réussissent et la fonction est responsable de toutes. Comment doit-elle libérer la grille ?
  1. Libérer le tableau de pointeurs, puis lire ses pointeurs de lignes.
  2. Libérer seulement le tableau de pointeurs, responsable aussi des lignes.
  3. Libérer les quatre lignes, puis le tableau de pointeurs.
  4. Libérer deux fois chaque ligne, puis le tableau de pointeurs.
Afficher la réponseMasquer la réponse
Réponse : C
Les lignes sont quatre allocations distinctes et leurs adresses sont stockées dans le tableau extérieur. Libère-les tant que ces adresses sont encore disponibles, puis libère le bloc extérieur. Libérer uniquement le tableau de pointeurs laisse les allocations des lignes non libérées ; le libérer en premier rend aussi invalides les lectures ultérieures de ses entrées.

Question 6 sur 10

Un redimensionnement de taille positive utilise int *tmp = realloc(data, bytes); et renvoie NULL. Le bloc d’origine était valide. Que peut faire maintenant le responsable du bloc ?
  1. Continuer à utiliser ou libérer le bloc d’origine inchangé.
  2. Utiliser les nouveaux octets à travers tmp après l’avoir remis à zéro.
  3. Libérer tmp pour libérer aussitôt le bloc d’origine.
  4. Utiliser data uniquement après une nouvelle allocation.
Afficher la réponseMasquer la réponse
Réponse : A
Un appel à realloc de taille positive qui échoue laisse l’allocation d’origine et son contenu inchangés. Comme le résultat a été reçu dans tmp, data contient toujours son adresse valide. Libérer le pointeur temporaire nul ne fait rien ; cela ne peut pas libérer le bloc encore détenu que désigne data.

Question 7 sur 10

Un tableau dynamique commence avec une longueur de 0 et une capacité de 3. Il ne double sa capacité que lorsqu’il est plein. Après huit ajouts réussis, quelles sont sa longueur et sa capacité ?
  1. Longueur 8, capacité 8
  2. Longueur 12, capacité 12
  3. Longueur 8, capacité 9
  4. Longueur 8, capacité 12
Afficher la réponseMasquer la réponse
Réponse : D
Le quatrième ajout porte la capacité de 3 à 6 et le septième de 6 à 12. Huit valeurs sont maintenant utilisées : la longueur est donc de 8. Une capacité de 8 correspondrait à un agrandissement à la taille exacte, pas à la règle de doublement indiquée ; la capacité libre n’augmente pas le nombre de valeurs stockées.

Question 8 sur 10

Après une allocation réussie, le code exécute int *view = owner; free(owner); owner = NULL;. Que se passe-t-il s’il lit ensuite view[0] ?
  1. Il lit un stockage valide, car view est non nul.
  2. Il accède à un objet dont la durée de vie est terminée.
  3. Il lit zéro, car owner a été rendu nul.
  4. Il crée une nouvelle allocation à l’adresse précédente.
Afficher la réponseMasquer la réponse
Réponse : B
view était un alias vers la même allocation, dont la durée de vie a pris fin avec free(owner). Affecter NULL à owner ne met à jour que cette variable pointeur. Cela ne modifie pas view et ne rétablit pas l’ancien bloc : la lecture ultérieure est donc une utilisation après libération qui provoque un comportement indéfini.

Question 9 sur 10

Un programme copie la chaîne « network » avec strcpy dans un tampon nouvellement alloué dans le tas. Quelle est la plus petite allocation nécessaire en octets C ?
  1. 7 octets
  2. 9 octets
  3. 16 octets
  4. 8 octets
Afficher la réponseMasquer la réponse
Réponse : D
Les sept lettres nécessitent sept octets et strcpy stocke aussi le caractère nul de fin de chaîne. Le plus petit tampon comporte donc huit octets. Sept octets contiendraient les lettres visibles, mais laisseraient le caractère de fin hors de l’allocation, provoquant une écriture hors limites même si l’affichage semblait fonctionner.

Question 10 sur 10

Une variable globale int *saved vaut initialement NULL. Une fonction auxiliaire exécute ce code puis se termine. Les entiers de type int occupent 4 octets. Memcheck signale une écriture invalide immédiatement après l’allocation. Quelle erreur de parcours et quelle catégorie de fuite à la fin en résultent ?

int *p = malloc(7 * sizeof *p);
if (p == NULL) return;
for (size_t i = 0; i <= 7; ++i) p[i] = (int)i;
saved = p;
  1. Un entier supplémentaire est écrit ; le bloc est encore accessible.
  2. Quatre entiers supplémentaires sont écrits ; le bloc est définitivement perdu.
  3. Un entier supplémentaire est écrit ; le bloc est définitivement perdu.
  4. Quatre entiers supplémentaires sont écrits ; le bloc est encore accessible.
Afficher la réponseMasquer la réponse
Réponse : A
Le bloc de sept entiers autorise les indices de zéro à six, mais la boucle écrit aussi à l’indice sept. La taille de l’accès est celle d’un entier de quatre octets. La variable globale conserve l’adresse de début après le retour de la fonction auxiliaire : le bloc non libéré est donc encore accessible, et non définitivement perdu.
Série 2 : Suivre les chemins d’allocation et de nettoyage10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Une fonction auxiliaire renvoie un tableau alloué avec succès par l’intermédiaire d’un pointeur local p. La fonction appelante stocke le résultat dans values. Qu’est-ce qui reste actif immédiatement après le retour de la fonction auxiliaire ?
  1. La variable p de la fonction auxiliaire et le tableau alloué.
  2. La variable p de la fonction auxiliaire et tous ses paramètres.
  3. Le tableau alloué et la variable values de la fonction appelante.
  4. La variable values de la fonction appelante, mais aucun tableau alloué.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Pour un nombre non nul n, quelle condition détecte avant l’allocation que n * sizeof *p n’est pas représentable dans size_t ?
  1. n > SIZE_MAX / sizeof *p
  2. n * sizeof *p > SIZE_MAX
  3. sizeof p > n / SIZE_MAX
  4. n > SIZE_MAX * sizeof *p
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Une fonction alloue le tampon a, le vérifie, puis tente d’allouer b. La seconde allocation échoue. La fonction est responsable de a et va immédiatement renvoyer un échec. Quel nettoyage faut-il effectuer ?
  1. Libérer b et revenir sans toucher à a.
  2. Rendre a nul, puis renvoyer un échec.
  3. Renvoyer un échec ; le nettoyage du pointeur local libère a.
  4. Libérer a avant de renvoyer un échec.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Une fonction déclare static int visits = 0; et exécute return ++visits;. Quelle valeur son troisième appel renvoie-t-il ?
  1. 1
  2. 3
  3. 0
  4. 2
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Un tampon de chaîne a une capacité de 12 octets et contient 11 caractères ainsi que son caractère de fin. Que faut-il faire avant d’ajouter un autre caractère ?
  1. Écraser le caractère de fin et laisser la taille inchangée.
  2. Augmenter deux fois la longueur, puis agrandir après l’ajout suivant.
  3. Écrire à l’indice 12 et garder l’ancien caractère de fin en place.
  4. Agrandir le tampon, puis écrire le caractère et celui de fin.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Une boucle alloue avec succès 48 octets à chacune de ses 750 itérations et perd le seul pointeur de chaque bloc sans le libérer. Quelle quantité de stockage fuit ?
  1. 36 000 octets
  2. 750 octets
  3. 48 000 octets
  4. 36 048 octets
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Un tableau alloué avec succès contient neuf éléments. Quel est son plus grand indice d’élément valide ?
  1. 9
  2. 8
  3. 10
  4. 7
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Un programme commence à planter dans free après une boucle qui remplit un tableau du tas. Que faut-il examiner avant de modifier l’appel de libération ?
  1. Les écritures antérieures qui ont pu dépasser les bornes du tableau.
  2. Si supprimer tout le nettoyage permet au programme de se terminer.
  3. Si un affichage modifie l’adresse de libération signalée.
  4. Si allouer le même tableau localement masque le plantage.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

L’adresse de début d’une allocation non libérée est encore stockée dans un pointeur global à la fin du programme. Quelle catégorie de fuite Valgrind la décrit ?
  1. Définitivement perdu
  2. Indirectement perdu
  3. Encore accessible
  4. Potentiellement perdu
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Un programme commence par ce bloc vérifié, puis choisit une branche selon p[5]. Un redimensionnement de taille positive a réussi. ASan ne signale rien, tandis que Memcheck signale une valeur non initialisée au niveau de la branche. Quelle correction correspond à tout cet historique ?

int *p = calloc(4, sizeof *p);
if (p == NULL) return EXIT_FAILURE;
int *tmp = realloc(p, 6 * sizeof *p);
if (tmp == NULL) { free(p); return EXIT_FAILURE; }
p = tmp;
  1. Réinitialiser les éléments 0 à 3 avant le choix de branche.
  2. Initialiser l’élément 4 mais laisser l’élément 5 inchangé.
  3. Initialiser les éléments 4 et 6 avant le choix de branche.
  4. Initialiser les éléments 4 et 5 avant d’utiliser leurs valeurs.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Série 3 : Croissance, durées de vie et indices des rapports10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Une fonction auxiliaire dispose d’un espace de travail local fixe de 20 entiers de type int, utilisé uniquement pendant l’appel et sans besoin de redimensionnement. Quel choix de stockage évite un responsable et un chemin de nettoyage inutiles ?
  1. Un bloc du tas dont l’adresse est perdue.
  2. Un pointeur renvoyé vers un tableau local.
  3. Un bloc du tas accessible par une variable globale sans responsable.
  4. Un tableau local ordinaire de 20 entiers de type int.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Sur un système où les pointeurs occupent 8 octets et les entiers de type int 4 octets, le code utilise int *p = malloc(11 * sizeof p);. Combien d’octets demande-t-il et quel est le problème de l’expression ?
  1. 44 octets ; elle mesure un élément au lieu du pointeur.
  2. 88 octets ; elle mesure le pointeur au lieu d’un élément.
  3. 11 octets ; sizeof supprime entièrement la multiplication.
  4. 88 octets ; elle réserve onze blocs aux responsabilités distinctes.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Après un appel réussi à calloc pour un tampon de caractères de taille positive, quelle chaîne C contient-il initialement ?
  1. Une chaîne vide
  2. Une chaîne d’espaces
  3. Une chaîne de « 0 »
  4. Une chaîne inconnue
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Un bloc de six entiers de type int est détenu par l’intermédiaire de p. Le code enregistre l’indice 4 et un pointeur vers cet élément. Une tentative d’agrandissement à neuf entiers échoue avec un pointeur temporaire. Une réduction ultérieure à trois réussit et le pointeur responsable est mis à jour. La sélection peut-elle encore être utilisée ?
  1. Oui ; l’agrandissement échoué a conservé la sélection et sa durée de vie.
  2. Oui ; il suffit de recalculer l’indice 4 à partir du bloc final de trois entiers.
  3. Non ; l’ancien pointeur est invalide et l’indice 4 ne tient plus dans le bloc.
  4. Non ; l’agrandissement échoué avait déjà libéré le bloc de six entiers.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Un tableau a une longueur de 6 et une capacité de 8. Deux valeurs sont ajoutées avec succès. Le code enregistre ensuite son pointeur responsable dans old, exécute cap = 16, puis data = realloc(data, cap * sizeof *data). Cette demande de taille positive échoue avant qu’une autre valeur soit stockée. Quel état rétablit le tableau inchangé ?
  1. Rétablir data = old, une longueur de 9 et une capacité de 16.
  2. Rétablir data = old, une longueur de 8 et une capacité de 8.
  3. Garder data = NULL, une longueur de 8 et une capacité de 8.
  4. Rétablir data = old, une longueur de 8 et une capacité de 16.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Une fonction détient sa seule allocation dans buffer. Elle exécute buffer = NULL; free(buffer); après sa dernière utilisation. Qu’arrive-t-il au bloc d’origine ?
  1. Il est libéré, car la variable contenait auparavant son adresse.
  2. Il est libéré et rempli d’octets nuls par l’affectation.
  3. Il reste alloué et son adresse est perdue.
  4. Il reste alloué, mais reste accessible à travers buffer.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Après une allocation vérifiée, une fonction libère son bloc à travers p, puis appelle plus tard free(alias), où alias a été copié depuis p avant la libération. De quelle erreur s’agit-il ?
  1. Une double libération à travers deux alias
  2. Une fuite de deux blocs actifs distincts
  3. Un nettoyage valide de deux pointeurs
  4. Une lecture non initialisée d’un nouveau stockage
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Un message de diagnostic est envoyé à stdout sans saut de ligne, puis le programme plante. Son texte n’apparaît jamais. Pourquoi ce fait ne permet-il pas de déterminer avec certitude jusqu’où l’exécution est allée ?
  1. Le texte absent prouve que l’appel d’affichage n’a jamais été exécuté.
  2. L’affichage a nécessairement empêché l’erreur de mémoire elle-même.
  3. L’absence de saut de ligne a nécessairement causé l’erreur de mémoire.
  4. Le texte pouvait encore se trouver dans le tampon de sortie.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Une personne a compilé program-asan avec AddressSanitizer et souhaite effectuer une exécution indépendante avec Memcheck. Quel exécutable doit-elle fournir à Memcheck ?
  1. La même version ASan avec la détection des fuites désactivée.
  2. Une version de débogage distincte sans AddressSanitizer.
  3. La même version ASan sans les pointeurs de cadres de pile.
  4. Un fichier source fourni directement à la place d’un exécutable.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

ASan s’arrête sur items[n] = 0;, juste après une allocation pour n entiers de type int. La boucle utilise i <= n, le résultat n’est pas vérifié et le nettoyage est absent. Quelle correction traite la cause et les défauts restants de propriété ?
  1. Vérifier l’allocation, boucler avec i < n, puis libérer après utilisation.
  2. Allouer n + 1 entiers, garder la borne et omettre le nettoyage.
  3. Vérifier l’allocation, garder i <= n, puis libérer avant la boucle.
  4. Garder la taille et la borne, mettre le bloc à zéro, puis le libérer.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Série 4 : Corriger la propriété lorsque les conditions changent10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Quelle est la durée de stockage d’un tableau global ordinaire de portée de fichier ?
  1. Une durée de stockage statique
  2. Une durée de stockage automatique
  3. Une durée de stockage allouée
  4. Une nouvelle durée à chaque appel
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Une bibliothèque a alloué quatre compteurs entiers avec calloc, puis a agrandi le bloc avec succès à sept compteurs. Que faut-il faire avant d’incrémenter le compteur 6 pour la première fois ?
  1. L’incrémenter directement, car tout agrandissement préserve la mise à zéro.
  2. Libérer et recréer les sept compteurs pour conserver les anciennes valeurs.
  3. Lui affecter la valeur initiale voulue avant de l’incrémenter.
  4. Rendre son adresse égale à NULL avant d’utiliser l’allocation agrandie.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Le tableau extérieur d’une grille contient des pointeurs vers des lignes dont les allocations sont détenues séparément. La fonction qui crée la grille a créé les lignes 0 et 1. Une fonction de lecture emprunte la ligne 0 jusqu’à son retour. La création de la ligne 2 échoue pendant que cette fonction de lecture est active. Les entrées suivantes n’ont jamais été initialisées. Quel nettoyage en cas d’échec respecte toutes les durées de vie ?
  1. Libérer les lignes 0 et 1, laisser la lecture se terminer, puis libérer le tableau extérieur.
  2. Laisser la lecture se terminer, libérer le tableau extérieur, puis lire et libérer ses lignes.
  3. Laisser la lecture se terminer, libérer toutes les lignes prévues, puis le tableau extérieur.
  4. Laisser la lecture se terminer, libérer les lignes 0 et 1, puis le tableau extérieur.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Une allocation de quatre entiers de type int est détenue par l’intermédiaire de p. Une tentative de redimensionnement à sept entiers échoue avec tmp et laisse p inchangé. Le code exécute ensuite p = realloc(p, 9 * sizeof *p), et cette demande échoue aussi. Il n’existe aucun alias. Quelle allocation est maintenant perdue ?
  1. Le premier bloc demandé de sept entiers reste alloué mais est perdu.
  2. Le bloc d’origine de quatre entiers reste alloué mais est perdu.
  3. Le second bloc demandé de neuf entiers reste alloué mais est perdu.
  4. Aucun bloc ne reste alloué, car les deux redimensionnements ont échoué.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

La capacité d’un tableau dynamique commence à 5 et double chaque fois qu’il se remplit. Le tableau est initialement vide et reçoit 18 valeurs. Si chaque agrandissement déplace le bloc, quelles sont la capacité obtenue et le nombre total maximal de copies d’éléments dues aux agrandissements ?
  1. Capacité 20 ; au plus 15 éléments copiés.
  2. Capacité 20 ; au plus 18 éléments copiés.
  3. Capacité 18 ; au plus 23 éléments copiés.
  4. Capacité 40 ; au plus 35 éléments copiés.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Une fonction alloue un résultat, le remplit, le libère, puis renvoie le pointeur à la fonction appelante. Celle-ci doit lire ce résultat. Quelle correction de propriété convient ?
  1. Renvoyer le résultat libéré et faire vérifier qu’il est non nul.
  2. Rendre le pointeur nul avant le retour et garder la même interface.
  3. Libérer deux fois le résultat pour qu’aucune allocation périmée ne subsiste.
  4. Renvoyer le résultat encore actif et laisser la fonction appelante le libérer.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Le code alloue char **labels = malloc(4 * sizeof *labels);, vérifie le résultat, puis exécute immédiatement strcpy(labels[0], "east");. Quelle solution corrige la première entrée tout en précisant qui est responsable de sa libération ?
  1. Allouer cinq emplacements de pointeurs, puis copier dans l’entrée 0 comme indiqué.
  2. Affecter le littéral « east » à l’entrée 0, puis l’écraser avec strcpy.
  3. Allouer cinq caractères pour l’entrée 0, vérifier, copier, puis libérer.
  4. Affecter NULL à l’entrée 0, puis y copier cinq caractères.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Un redimensionnement réussi réduit un bloc de neuf entiers de type int initialisés à quatre. Quel contenu est garanti d’être conservé ?
  1. Les valeurs d’origine aux indices 5 à 8.
  2. Les valeurs d’origine aux indices 0 à 3.
  3. Les neuf valeurs, tant qu’aucune nouvelle allocation n’a lieu.
  4. Quatre nouvelles valeurs nulles remplaçant la partie initiale.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Un test provoque un dépassement d’un élément d’un tableau local, qui reste dans une mémoire de pile par ailleurs valide. Une exécution de Memcheck ne le détecte pas. Quelle vérification suivante convient le mieux à ces indices ?
  1. Relancer Memcheck en affichant les catégories de fuites.
  2. Recompiler avec ASan et relancer les mêmes données d’entrée.
  3. Considérer l’accès comme valide après une exécution sans erreur.
  4. Le remplacer par un bloc du tas plus grand et non vérifié.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Un outil de vérification signale une double libération. Supprimer les deux appels de libération empêche le plantage, mais laisse une allocation non libérée. Quelle correction doit remplacer cette modification ?
  1. Ne garder aucune libération et compter sur le système à la fin.
  2. Garder les deux libérations, mais rendre nul un alias emprunté.
  3. Déplacer les deux libérations avant la dernière utilisation.
  4. Garder une libération après la dernière utilisation, sous un seul responsable.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Série 5 : Suivre des historiques de mémoire complets10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Une fonction renvoie l’adresse d’un tableau local de caractères. La fonction appelante affiche parfois le texte attendu et plante parfois. Qu’est-ce qui explique ces deux résultats ?
  1. Le tableau local est devenu statique parce que son adresse est sortie de la fonction.
  2. Le retour a transféré la propriété d’une allocation valide dans le tas.
  3. Le texte attendu prouve que le tableau existait pendant cette exécution.
  4. Le tableau a cessé d’exister au retour ; l’accès de la fonction appelante est invalide.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Pour un double *samples ordinaire, que mesure sizeof samples ?
  1. La taille d’une valeur double pointée.
  2. Le nombre d’octets de toute l’allocation.
  3. La taille de la variable pointeur.
  4. Le nombre d’éléments initialisés du tableau.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Une fonction renvoie une chaîne nouvellement allouée. La fonction appelante la passe à une fonction d’affichage qui ne fait que l’emprunter ; celle-ci la libère par erreur. La fonction appelante la lit ensuite et la libère. Quelle modification de propriété corrige tout le chemin ?
  1. Supprimer la libération par la fonction appelante et garder sa lecture après l’affichage.
  2. Supprimer la libération par la fonction d’affichage et garder la libération finale par la fonction appelante.
  3. Garder les deux libérations et ne rendre nul que le pointeur local de la fonction d’affichage.
  4. Déplacer la libération par la fonction appelante avant l’affichage pour terminer plus tôt la propriété.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Memcheck signale qu’un saut conditionnel dépend d’une valeur non initialisée. Quelle option aide à trouver l’origine de cette valeur ?
  1. --track-origins=yes
  2. --leak-check=no
  3. --show-leak-kinds=all
  4. --error-limit=no
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Un tampon de texte commence avec une capacité de 6, une longueur de 4 et un caractère de fin à l’indice 4. Il ajoute X avec succès sans agrandissement et met à jour son caractère de fin. Le caractère suivant Y déclenche un agrandissement avec un pointeur temporaire, mais la demande échoue. L’API promet de préserver la chaîne en cas d’échec. Quel état subsiste ?
  1. Longueur 4, capacité 6 ; X et Y sont tous deux supprimés.
  2. Longueur 5, capacité 6 ; X est conservé et le caractère de fin est à l’indice 5.
  3. Longueur 6, capacité 6 ; Y remplace le caractère de fin à l’indice 5.
  4. Longueur 5, capacité 12 ; X est conservé et le caractère de fin est à l’indice 5.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Un service traite trois tâches. Chacune alloue avec succès 24 octets dans la variable locale a et 40 octets dans la variable locale b. Sur le chemin d’erreur, elle libère a, exécute b = NULL et revient. Aucun alias ne subsiste. Quelle est la taille totale des allocations perdues et quelle modification l’évite ?
  1. 192 octets ; libérer les deux variables pointeurs après leur retour.
  2. 72 octets ; cesser de libérer a sur le chemin d’erreur.
  3. 40 octets ; ne libérer que le b de la dernière tâche après la boucle.
  4. 120 octets ; libérer chaque bloc b avant de rendre son pointeur nul.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Un pointeur emprunté désigne l’élément 2 d’un tableau. Le code agrandit le tableau avec succès à l’aide d’un pointeur temporaire et met à jour le pointeur responsable, puis écrit à travers l’ancien pointeur emprunté. L’adresse n’a pas changé. Quelle règle rend tout de même cet accès invalide ?
  1. Tout redimensionnement supprime le contenu initial d’origine.
  2. Le pointeur responsable et le pointeur emprunté doivent avoir des adresses différentes.
  3. Le redimensionnement réussi a mis fin à la durée de vie de l’ancien objet.
  4. L’agrandissement ne conserve que les valeurs atteintes par l’indice zéro.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Les entiers de type int occupent 4 octets et les pointeurs 8 octets. Le code utilise int *p = malloc(6 * sizeof p);, vérifie la réussite, initialise les éléments d’indices 0 à 5, puis additionne ceux d’indices 0 à 11. Quel diagnostic convient à toute la somme ?
  1. Les six dernières lectures restent dans les bornes mais utilisent des valeurs non initialisées.
  2. Les six dernières lectures sont hors d’une allocation de 24 octets.
  3. Les douze lectures sont valides, car l’allocation les initialise.
  4. Seule la dernière lecture dépasse la fin de l’allocation trop grande.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Un programme s’arrête sur une erreur de segmentation à une ligne qui lit p[index]. Que faut-il vérifier au sujet de cet accès ?
  1. L’origine du pointeur, la durée de vie de sa cible et la plage de l’indice.
  2. Uniquement l’indice, car tout pointeur non nul est actif.
  3. Uniquement la ligne de libération, car toutes les erreurs sont des doubles libérations.
  4. Uniquement la mise en tampon, car une lecture de tableau ne peut pas provoquer d’erreur.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Le rapport d’un outil montre une lecture dans display, une libération antérieure dans reset et une allocation dans load. La fonction appelante invoque load, reset, puis display sur le même résultat. Elle a besoin de cet affichage. Quelle correction découle de l’histoire du bloc ?
  1. Augmenter la taille dans load sans changer l’ordre des appels.
  2. Ne rendre nul que le pointeur local de reset et garder la lecture ultérieure.
  3. Garder le résultat actif pendant display, puis le libérer.
  4. Ignorer reset, car l’erreur signalée s’est produite dans display.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Tu bloques sur ton propre exercice ? Aide aux Devoirs

Mots-Clés

63 Mots-Clés, chacun avec sa définition

A

AddressSanitizer
AddressSanitizer est une instrumentation ajoutée par le compilateur pour détecter les accès à la mémoire et les libérations invalides pendant l’exécution d’un exécutable instrumenté.
Une version ASan peut identifier le dépassement d’un tableau local qui reste dans une mémoire de pile par ailleurs valide.
alias
Un alias est un autre pointeur vers le même objet, qui partage la durée de vie de cet objet sans être mis à jour lorsqu’une autre variable pointeur change.
Affecter NULL au pointeur responsable après la libération laisse tous les alias enregistrés inutilisables pour des lectures ultérieures.
allocation
Une allocation est une demande de réservation d’un bloc de mémoire ; lorsqu’elle réussit, elle fournit son adresse de début pour une utilisation et une libération ultérieures.
Un programme vérifie la réussite de son allocation avant de stocker le premier élément du tableau demandé.
allocation imbriquée
Une allocation imbriquée est une organisation comportant des blocs intérieurs alloués séparément dont les adresses sont conservées dans un conteneur extérieur alloué.
Une grille à lignes distinctes nécessite le nettoyage des lignes avant la libération du tableau extérieur de pointeurs.
allocation perdue
Une allocation perdue est un bloc toujours réservé dont l’adresse utilisable n’est plus conservée, ce qui empêche le programme de le libérer normalement.
Rendre nul le seul pointeur responsable avec NULL sans appeler d’abord free fait perdre l’adresse de l’allocation.

B

bilan du tas
Un bilan du tas indique l’activité d’allocation et l’espace restant dans le tas à la fin du programme, ce qui aide à examiner les blocs non libérés.
Lis les totaux des blocs restants avec les catégories de fuites au lieu de considérer chaque bloc comme perdu.

C

calloc
La fonction calloc réserve un tableau à partir d’un nombre et d’une taille d’élément, met ses octets à zéro et renvoie un pointeur nul si l’allocation échoue.
Les compteurs entiers obtenus par une demande calloc vérifiée commencent à zéro avant leur première incrémentation.
capacité
La capacité d’un tableau est le nombre d’emplacements d’éléments réservés dans son bloc, y compris ceux qui sont inutilisés et ne font pas encore partie de son contenu logique.
Une demande d’agrandissement échouée doit laisser la capacité inchangée, car aucun emplacement supplémentaire n’a été obtenu.
caractère nul de fin de chaîne
Un caractère nul de fin de chaîne est le caractère de valeur zéro qui marque la fin d’une chaîne C et occupe un octet après ses caractères visibles.
La chaîne « west » de quatre caractères nécessite cinq octets lorsqu’elle est copiée avec son caractère de fin.
chemin de nettoyage
Un chemin de nettoyage est un code qui libère les ressources obtenues avant la sortie d’une fonction, notamment lorsque le travail s’arrête après une réussite partielle.
Si une seconde allocation échoue, le chemin d’erreur libère le premier bloc avant de renvoyer un échec.
comportement indéfini
Un comportement indéfini se produit lorsque le C n’impose aucune exigence sur le résultat du programme après une violation des règles, ce qui permet des plantages ou une corruption silencieuse.
Une écriture hors limites peut sembler sans conséquence lors d’un test et corrompre une autre valeur avec des données d’entrée différentes.
croissance géométrique
La croissance géométrique augmente la capacité par un facteur multiplicatif, par exemple en la doublant, afin de limiter les copies répétées au fil de nombreux ajouts.
Un tableau plein de capacité quatre passe à huit, puis à seize lorsque sa limite d’agrandissement suivante est atteinte.

D

débordement de pile
Un débordement de pile épuise l’espace disponible pour les appels actifs et les objets automatiques, souvent à cause d’une récursivité profonde ou d’un très grand tableau local.
Une fonction récursive non optimisée sans condition d’arrêt peut continuer à ajouter des cadres jusqu’à échouer.
débordement de tampon dans le tas
Un débordement de tampon dans le tas est une lecture ou une écriture avant ou après les bornes d’un bloc alloué, même si l’adresse environnante se trouve être accessible.
Une boucle qui atteint l’indice dix dans une allocation de dix éléments franchit la plage d’indices valides du bloc.
débordement du calcul de taille
Un débordement du calcul de taille se produit lorsqu’un calcul convertissant un nombre d’éléments en octets dépasse la plage de size_t et reboucle vers une valeur représentable plus petite.
Compare le nombre d’éléments à SIZE_MAX / sizeof *p avant de le multiplier par la taille d’un élément.
définitivement perdu
Une allocation est définitivement perdue dans Memcheck lorsqu’aucun pointeur ne subsiste : son espace réservé ne peut donc être libéré à partir d’aucune adresse conservée.
Une fonction auxiliaire qui abandonne son seul pointeur responsable peut laisser un bloc définitivement perdu dans le rapport de fin.
donnée d’entrée reproduisant l’erreur
Une donnée d’entrée reproduisant l’erreur est un cas de test qui atteint de manière fiable le problème observé et fournit un point de départ concret pour le débogage et la vérification.
Réduis une donnée d’entrée qui provoque un échec tout en conservant l’erreur afin de faciliter l’interprétation du premier rapport pertinent.
double libération
Une double libération tente de libérer un bloc dont la durée de vie a déjà pris fin lors d’une libération précédente, y compris à travers un autre alias.
Un responsable et un emprunteur ne peuvent pas chacun appeler free sur ce qui était une seule allocation active partagée.
durée de stockage
La durée de stockage d’un objet détermine combien de temps son espace est réservé : elle peut notamment prendre fin à la fin d’un bloc, lors d’une libération ou à la fin du programme.
Un pointeur local et son tableau alloué peuvent avoir des durées de stockage différentes même lorsqu’ils partagent une adresse.
durée de vie d’un objet
La durée de vie d’un objet est la période pendant laquelle il existe dans un espace réservé et peut être atteint par des pointeurs valides.
Un appel réussi à realloc met fin à la durée de vie de l’ancien objet même si l’adresse renvoyée semble inchangée.

E

encore accessible
Dans Valgrind, une allocation est encore accessible lorsqu’un pointeur subsiste à la fin du programme, bien que celui-ci n’ait pas libéré le bloc.
Un pointeur global qui conserve l’adresse de début d’une allocation non libérée la rend accessible à la vérification de fin.
erreur de décalage d’une unité
Une erreur de décalage d’une unité rend une borne trop grande ou trop petite d’une position, en incluant souvent le nombre d’éléments comme dernier indice invalide.
Remplacer i <= n par i < n permet à un parcours commençant à zéro de visiter exactement les n éléments prévus.
erreur de segmentation
Une erreur de segmentation est une erreur signalée par le système d’exploitation à la suite d’un accès mémoire interdit, comme le déréférencement courant d’un pointeur nul ou une écriture dans une zone en lecture seule.
Une écriture invalide peut d’abord corrompre des données, puis provoquer une erreur de segmentation plus tard dans une autre fonction.
exécution de vérification
Une exécution de vérification lance le programme corrigé avec un outil de vérification pour confirmer le chemin testé ; elle est suivie de tests avec des données d’entrée qui parcourent d’autres chemins.
Après un cas de reproduction sans erreur, teste une entrée vide, les limites d’agrandissement et les sorties anticipées pour repérer les nettoyages manquants.

F

free
La fonction free libère un bloc alloué encore actif pour qu’il puisse être réutilisé lorsqu’elle reçoit son adresse de début, et ne fait rien lorsqu’elle reçoit NULL.
La fonction appelante ne libère une copie de chaîne renvoyée qu’après que tous les emprunteurs ont terminé de la lire.
fuite de mémoire
Une fuite de mémoire est une allocation qui n’est plus nécessaire mais reste non libérée, souvent parce que le programme a perdu son dernier pointeur vers elle.
Écraser le seul pointeur responsable avec une nouvelle allocation peut abandonner définitivement l’ancien bloc.

I

indirectement perdu
Une allocation est indirectement perdue lorsqu’elle n’est accessible qu’à travers un autre bloc lui-même perdu, comme le signale Memcheck.
La perte d’un tableau extérieur contenant les adresses de lignes dont le programme est responsable peut aussi rendre les allocations des lignes indirectement perdues.
initialisation
L’initialisation établit une valeur avant sa lecture, par une affectation initiale ou une allocation mise à zéro appropriée.
Un compteur doit avoir une valeur initiale définie, car son incrémentation lit d’abord sa valeur précédente.
instrumentation
L’instrumentation ajoute du code de vérification pendant la compilation afin que le programme obtenu puisse détecter certaines erreurs lors de l’exécution des chemins testés.
Ajouter -fsanitize=address à la compilation et à l’édition de liens crée un exécutable instrumenté par ASan.

L

LeakSanitizer
LeakSanitizer recherche les allocations perdues à la fin du programme et peut accompagner ASan ; sa disponibilité et ses paramètres par défaut dépendent de la plateforme.
Les versions ASan sous Linux activent normalement la recherche de fuites ; certaines chaînes d’outils macOS ne peuvent pas la fournir.
lecture d’une valeur non initialisée
Une lecture d’une valeur non initialisée utilise une valeur qui n’a jamais été définie, notamment celle d’un élément dans les bornes d’un espace nouvellement alloué ou agrandi.
Additionner trois entiers de type int alloués après n’avoir affecté de valeur qu’à deux d’entre eux lit tout de même une troisième valeur non initialisée.
littéral de chaîne de caractères
Un littéral de chaîne de caractères est un texte écrit entre guillemets dans le code source C, dont le stockage est statique et qui ne doit pas être modifié à travers un pointeur.
Utilise char word[] = "hello"; lorsqu’une copie locale modifiable est nécessaire plutôt qu’un pointeur vers le littéral.
longueur
La longueur d’un tableau est son nombre d’éléments initialisés actuellement utilisés ; elle détermine la plage des calculs sur les valeurs stockées.
Après l’ajout d’une cinquième valeur dans l’espace disponible, la longueur devient cinq même si la capacité reste de huit.

M

malloc
La fonction malloc réserve un nombre demandé d’octets et renvoie l’adresse d’un bloc ou un pointeur nul, sans initialiser les valeurs stockées.
Un tableau d’entiers obtenu avec malloc nécessite des affectations avant que ses éléments soient additionnés ou comparés.
Memcheck
Memcheck est l’outil de vérification de la mémoire par défaut de Valgrind ; il suit les accès invalides, les durées de vie des allocations, les valeurs non initialisées et les blocs non libérés pendant une exécution.
Exécute un exécutable de débogage ordinaire sous Memcheck pour obtenir les lignes du code source avec les historiques de mémoire.
mémoire mise à zéro
Une mémoire mise à zéro a tous ses bits à zéro, ce qui donne le zéro entier sans garantir de façon portable le zéro à virgule flottante ou les pointeurs nuls.
Un tampon de caractères de taille positive obtenu avec calloc commence par un caractère nul et contient une chaîne vide.
MemorySanitizer
MemorySanitizer est l’outil de Clang qui détecte l’utilisation de valeurs non initialisées sur les systèmes pris en charge, un type d’erreur que l’ASan ordinaire ne suit pas.
Utilise-le dans une version distincte prise en charge au lieu d’associer son instrumentation à celle d’AddressSanitizer.
mise en tampon des sorties
La mise en tampon des sorties conserve temporairement le texte à afficher en mémoire avant de l’écrire ; un plantage peut donc empêcher l’apparition de messages de progression récents.
Appeler fflush(stdout) après un message de progression rend son texte placé en tampon disponible avant l’opération suivante.

P

pile d’appels
La pile d’appels est la région couramment utilisée pour suivre les appels de fonctions actifs, leurs objets locaux, leurs paramètres et leurs informations de retour.
Une fonction récursive non optimisée peut continuer à ajouter des cadres jusqu’à épuiser l’espace disponible dans la pile.
point d’allocation
Un point d’allocation est l’endroit du programme qui demande un bloc, indiqué dans les rapports d’outils pour établir où cet espace a été créé.
Le point d’allocation d’un rapport de fuite peut se trouver dans une fonction auxiliaire, tandis que la bonne libération revient à la fonction appelante.
pointeur emprunté
Un pointeur emprunté donne accès à un objet encore actif, tandis qu’une autre partie du programme reste responsable de décider quand le libérer.
Une fonction d’affichage peut emprunter une chaîne pendant son appel sans obtenir le droit de libérer le bloc de la fonction appelante.
pointeur intérieur
Un pointeur intérieur désigne une position à l’intérieur d’un objet, par exemple un élément de tableau, plutôt que l’adresse de début de son allocation.
Conserve un indice d’élément pendant un redimensionnement réussi et recalcule le pointeur intérieur à partir du résultat.
pointeur nul
Un pointeur nul est une valeur de pointeur qui ne désigne aucun objet, renvoyée par les fonctions d’allocation lorsqu’un bloc non nul demandé ne peut pas être fourni.
Une vérification du pointeur nul dirige l’exécution vers un chemin d’erreur avant que le code tente d’accéder à une allocation échouée.
pointeur pendant
Un pointeur pendant désigne un objet dont la durée de vie est terminée : son ancienne cible n’est donc plus valide pour un accès ultérieur à travers ce pointeur.
Un pointeur vers l’ancien tableau local d’une fonction auxiliaire est pendant dès le retour de celle-ci.
pointeur temporaire
Un pointeur temporaire reçoit le résultat d’un redimensionnement avant la mise à jour du pointeur responsable, ce qui préserve l’adresse de l’ancienne allocation si la demande échoue.
Avec tmp = realloc(p, bytes), un résultat nul laisse p disponible pour continuer à l’utiliser ou le nettoyer.
potentiellement perdu
Une allocation est potentiellement perdue dans Memcheck lorsque seul un pointeur intérieur a été trouvé, ce qui laisse une incertitude sur l’adresse de début de l’allocation.
Vérifie si un curseur mobile a remplacé le pointeur responsable au lieu de préserver un pointeur de début distinct.
première erreur signalée
La première erreur signalée est le premier problème détecté par un outil de vérification pendant une exécution ; elle constitue souvent le meilleur point de départ, car les échecs ultérieurs peuvent découler des dégâts qu’elle a causés.
Corrige le débordement initial avant d’examiner chaque échec ultérieur de l’allocateur dans le même rapport.
propriété
La propriété est la responsabilité de libérer un bloc alloué après sa dernière utilisation ou de transférer explicitement cette tâche à une autre partie du programme.
Une fonction qui produit une copie transfère la propriété à la fonction appelante lorsqu’elle renvoie un résultat nouvellement alloué.

R

realloc
La fonction realloc remplace un bloc alloué par un objet de la taille demandée, en conservant la partie initiale qui y tient et en pouvant changer son adresse.
Agrandir un bloc de quatre entiers de type int à six conserve les quatre premières valeurs, mais laisse deux nouveaux éléments non initialisés.
réutilisation de tampon
La réutilisation de tampon consiste à conserver une allocation adaptée pour des opérations répétées, en remplaçant son contenu avant de libérer finalement le bloc.
Une boucle peut réutiliser un espace de travail de 100 octets au lieu d’allouer et d’abandonner un bloc à chaque passage.

S

size_t
Le type size_t est le type entier non signé utilisé pour les tailles d’objets et les nombres d’octets à allouer, notamment pour le résultat de sizeof.
Un tableau dynamique utilise des variables size_t pour sa longueur et sa capacité afin qu’elles puissent décrire des tailles d’objets.
sizeof
L’opérateur sizeof donne la taille d’un type ou d’un objet en octets C ; appliqué à un pointeur, il mesure le pointeur et non l’allocation qu’il vise.
La demande n * sizeof *p suit le type de l’élément, tandis que sizeof p ne permet pas de retrouver la longueur du tableau.
stockage automatique
Le stockage automatique concerne les objets locaux ordinaires et les paramètres dont la durée de vie prend fin lorsque l’exécution quitte leur bloc.
Le tableau local d’une fonction auxiliaire cesse d’exister au retour de celle-ci, même si la fonction appelante a conservé son adresse.
stockage statique
Le stockage statique est un stockage qui dure pendant toute l’exécution du programme et que les objets globaux et les variables locales déclarées avec static utilisent.
Un compteur statique dans une fonction conserve sa valeur mise à jour au début de l’appel suivant.
suivi de l’origine
Le suivi de l’origine remonte d’une valeur non initialisée vers le stockage qui l’a fournie, ce qui aide à trouver l’affectation manquante.
L’option --track-origins=yes de Memcheck peut expliquer pourquoi une branche ultérieure dépend de données du tas non initialisées.

T

tableau de longueur variable
Un tableau de longueur variable est un tableau dont la taille est fixée pendant l’exécution et dont la durée de vie est automatique ; il occupe généralement un espace dans la pile.
Déclarer int a[n] ne fournit pas le résultat nul permettant de vérifier l’échec d’une allocation dans le tas.
tableau dynamique
Un tableau dynamique est un espace alloué pour des éléments, suivi avec une longueur et une capacité afin de pouvoir s’étendre lorsque de nouvelles valeurs arrivent.
Un tableau dynamique peut contenir dix valeurs dans seize emplacements, ce qui laisse de la capacité libre pour des ajouts ultérieurs.
tampon fourni par la fonction appelante
Un tampon fourni par la fonction appelante est un espace de stockage donné à une fonction pour sa sortie, ce qui permet à la fonction appelante de garder le contrôle de sa taille et de sa durée de vie.
Une fonction appelante peut passer son propre tableau de 16 octets à une fonction auxiliaire qui le remplit avec une courte salutation.
tampon statique partagé
Un tampon statique partagé est un espace de stockage persistant pour le résultat d’une fonction, réutilisé par les appels, si bien qu’un appel ultérieur peut écraser un résultat précédent.
Deux pointeurs renvoyés par des appels successifs peuvent désigner le même tableau statique plutôt que des textes indépendants.
tas
Le tas est la réserve de mémoire dans laquelle l’allocateur fournit des blocs dont la durée de vie est contrôlée par l’allocation et la libération.
Renvoyer l’adresse d’un tableau alloué laisse le bloc dans le tas pour que la fonction appelante l’utilise et le libère.
trace de pile
Une trace de pile liste les appels de fonctions actifs lors d’un plantage ou d’une erreur et aide à repérer l’accès pertinent ainsi que le chemin qui y a mené.
Commence par le premier cadre pertinent dans ton propre code, puis examine comment son pointeur a obtenu sa valeur.

U

UndefinedBehaviorSanitizer
UndefinedBehaviorSanitizer ajoute des vérifications pour certains comportements indéfinis qui ne concernent pas les adresses, comme le débordement d’entier signé, et peut accompagner ASan.
Une version de développement peut utiliser -fsanitize=address,undefined pour activer les deux ensembles de vérifications compatibles.
utilisation après libération
Une utilisation après libération accède à un objet alloué après sa libération, ce qui peut renvoyer des données qui semblent anciennes, corrompre la mémoire ou provoquer un plantage.
Afficher une chaîne libérée à travers un alias enregistré est invalide, même si les mots attendus apparaissent.

À propos de cette unité

Par StudeliSignaler une erreur

Crée ton propre Matériel d'Étude

Transforme tes notes, diapos et documents en Guides d'Étude, Questions Pratiques, Flashcards et 6 autres outils.

Commencer