ProgrammationDéveloppeur C embarqué

Comment fonctionne la gestion de la mémoire en langage C lors de l'utilisation de tableaux, de structures et de pointeurs ? Comment éviter les fuites de mémoire et la corruption des données ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Historique de la question

En langage C, le programmeur gère la mémoire directement : l'allocation, la désallocation et l'utilisation des tableaux, des structures et des pointeurs sont contrôlées manuellement. Le mécanisme d'allocation dynamique de mémoire existe en C depuis les années 1970 et est implémenté via des fonctions spécifiques de la bibliothèque standard (malloc, calloc, realloc, free). Cette approche offre des performances et de la flexibilité, mais nécessite de la prudence.

Problème

Une erreur dans la gestion de la mémoire peut entraîner des fuites, la corruption d'autres données, des plantages de programme ou des vulnérabilités de sécurité. Les erreurs surviennent souvent à cause d'appels free oubliés, de débordements de tableaux, de castings de pointeurs incorrects ou de double désallocation. Pour les structures, la situation est similaire, mais le risque d'oublier de nettoyer les champs dynamiques imbriqués s'ajoute.

Solution

Pour minimiser les erreurs, il est recommandé de développer un code strictement structuré, de suivre toutes les allocations et désallocations de mémoire, de ne pas utiliser des pointeurs désalloués et de surveiller la taille des tableaux alloués. Il est important d'utiliser des outils d'analyse statique, des vérifications finales (valgrind, sanitizers), ainsi que de respecter les conventions : celui qui appelle malloc doit aussi libérer la mémoire.

Exemple de code :

#include <stdio.h> #include <stdlib.h> typedef struct { int *arr; size_t size; } ArrayWrapper; ArrayWrapper *create(size_t n) { ArrayWrapper *aw = malloc(sizeof(ArrayWrapper)); if (!aw) return NULL; aw->arr = malloc(sizeof(int) * n); if (!aw->arr) { free(aw); return NULL; } aw->size = n; return aw; } void destroy(ArrayWrapper *aw) { if (aw) { free(aw->arr); free(aw); } }

Caractéristiques clés :

  • Les tableaux et structures dynamiques peuvent être créés de n'importe quelle taille à l'exécution.
  • Le code qui a obtenu la mémoire allouée en est responsable.
  • La clé de la sécurité est de libérer la mémoire immédiatement après l'utilisation, sans permettre les doubles libérations.

Questions pièges.

Que se passe-t-il lors de la désallocation de la mémoire via un pointeur nul (free(NULL)) ?

Selon la norme C, l'appel de free sur un pointeur nul est sûr - rien ne se passe, aucune erreur ne se produit. C'est pratique pour transférer la responsabilité de la libération de la mémoire.

Peut-on utiliser la mémoire après un appel à free ?

Non, utiliser la mémoire après sa libération (use-after-free) est une erreur classique. Les données peuvent changer ou la zone peut être réattribuée à un autre processus. Il est toujours conseillé de mettre le pointeur à NULL après un free.

int *ptr = malloc(10); free(ptr); ptr = NULL; // Sûr

Est-il nécessaire de libérer toute la mémoire allouée avant la sortie du programme ?

Techniquement non - à la fin du programme, le système d'exploitation libère toutes les ressources. Mais ignorer la libération de la mémoire est un anti-modèle qui complique le débogage et peut entraîner des erreurs dans de gros programmes s'exécutant longtemps.

Erreurs typiques et anti-modèles

  • Perte de pointeur vers la mémoire allouée (fuite de mémoire).
  • Double libération de mémoire, use-after-free.
  • Mauvaise taille de mémoire allouée (sizeof de type incorrect).

Exemple de la vie réelle

Cas négatif

Un développeur crée des tableaux dynamiques à l'intérieur d'une fonction, oubliant de les nettoyer :

Avantages :

  • Implémentation rapide, absence d'erreurs sur de petits volumes.

Inconvénients :

  • Fuites de mémoire avec de grandes données, plantages du programme, bugs irréparables.

Cas positif

Tout le code passe par une vérification d'analyse statique, tous les pointeurs sont remis à NULL après free, chaque malloc est accompagné d'un free :

Avantages :

  • Facilité de maintenance, faible probabilité de bugs.

Inconvénients :

  • Un peu plus de lignes de code.