Zéro leak. Zéro erreur. Zéro compromis.
Valgrind est un framework d'analyse dynamique de programmes. Son outil principal, Memcheck, surveille chaque accès mémoire de ton programme en temps réel et détecte les fuites mémoire, les accès invalides, les utilisations de mémoire non initialisée, etc.
Valgrind c'est un anti-triche invisible dans ton jeu. Il enveloppe ton programme entier et surveille chaque octet de mémoire que tu touches. Si tu accèdes à une zone interdite, si tu oublies de libérer une zone, si tu lis une valeur que tu n'as jamais écrite — il te le signale immédiatement avec l'adresse exacte et la pile d'appels.
Outil principal. Détecte leaks et accès invalides
Profiling de performance et comptage d'instructions
Profiling du heap — suivre l'usage mémoire dans le temps
# Installer Valgrind
sudo apt-get install valgrind
# Vérifier la version
valgrind --version
valgrind-3.18.1
# ⚠️ OBLIGATOIRE — Compiler avec -g pour avoir les numéros de lignes
gcc -Wall -Werror -Wextra -g main.c -o prog
# Sans -g, Valgrind signale les erreurs mais sans numéros de lignes :
==123== at 0x401130: main (in /home/user/prog) ← adresse seulement
# Avec -g :
==123== at 0x401130: main (main.c:12) ← ligne précise ✅
# Commande de base (Memcheck implicite)
valgrind ./prog
# ✅ Commande RECOMMANDÉE Holberton — la plus complète
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose ./prog
# Avec des arguments (argv)
valgrind --leak-check=full ./prog arg1 arg2
# Sauvegarder le rapport dans un fichier
valgrind --leak-check=full --log-file=rapport.txt ./prog
# Spécifier l'outil explicitement
valgrind --tool=memcheck --leak-check=full ./prog
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose ./prog — à connaître par cœur.| Option | Ce qu'elle fait |
|---|---|
| --leak-check=no | Pas de rapport de leaks (rapide, juste les erreurs d'accès) |
| --leak-check=summary | Résumé des leaks seulement (défaut) |
| --leak-check=full | ✅ Rapport complet avec stack trace pour chaque leak |
| --show-leak-kinds=all | ✅ Affiche TOUS les types de leaks (definitely, indirectly, possibly, reachable) |
| --show-leak-kinds=definite | Affiche seulement les leaks certains |
| --track-origins=yes | ✅ Trace l'origine des valeurs non initialisées (plus lent mais précieux) |
| --verbose | ✅ Mode verbeux — plus d'informations dans le rapport |
| --error-exitcode=N | Retourne le code N si des erreurs sont détectées (utile en CI/CD) |
| --log-file=fichier.txt | Sauvegarder le rapport dans un fichier |
| --suppressions=fichier | Ignorer certaines erreurs connues (bibliothèques tierces) |
| --tool=callgrind | Utiliser un autre outil que Memcheck |
| --num-callers=N | Nombre de frames à afficher dans la stack trace (défaut: 12) |
==12345== ← PID du processus (change à chaque exécution)
==12345== Memcheck, a memory error detector
==12345== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==12345== Using Valgrind-3.18.1 and LibVEX; ...
==12345==
# ── SI TON PROGRAMME A DES ARGUMENTS ──────────────────
==12345== Command: ./prog arg1 arg2
# ── UNE ERREUR D'ACCÈS INVALIDE ────────────────────────
==12345== Invalid read of size 4 ← type d'erreur + taille
==12345== at 0x401155: main (main.c:12) ← où (fichier:ligne)
==12345== Address 0x5204040 is 0 bytes after a block of size 16 alloc'd
==12345== at 0x4848899: malloc (in .../vgpreload_memcheck...)
==12345== by 0x401140: main (main.c:8) ← où le malloc a été fait
# ── LE HEAP SUMMARY ────────────────────────────────────
==12345== HEAP SUMMARY:
==12345== in use at exit: 40 bytes in 1 blocks ← mémoire non libérée
==12345== total heap usage: 3 allocs, 2 frees, 72 bytes allocated
↑ 3 malloc, 2 free → 1 oublié !
# ── LE LEAK SUMMARY ────────────────────────────────────
==12345== LEAK SUMMARY:
==12345== definitely lost: 40 bytes in 1 blocks ← leak certain
==12345== indirectly lost: 0 bytes in 0 blocks
==12345== possibly lost: 0 bytes in 0 blocks
==12345== still reachable: 0 bytes in 0 blocks
==12345== suppressed: 0 bytes in 0 blocks
# ── L'ERROR SUMMARY ────────────────────────────────────
==12345== ERROR SUMMARY: 2 errors from 2 contexts ← résumé final
# ── RÉSULTAT PARFAIT ───────────────────────────────────
==12345== All heap blocks were freed -- no leaks are possible
==12345== ERROR SUMMARY: 0 errors from 0 contexts ← ✅ parfait !
void func() {
int *p = malloc(40);
/* p sort de scope → adresse perdue → DEFINITELY LOST */
}
definitely lost: 40 bytes in 1 blocks
typedef struct { int *data; } Node;
Node *n = malloc(sizeof(Node));
n->data = malloc(10);
/* Si on perd n → n->data est INDIRECTLY LOST */
indirectly lost: 10 bytes in 1 blocks
int *arr = malloc(5 * sizeof(int));
arr++; /* ptr ne pointe plus sur le début ! */
/* free(arr) serait faux → POSSIBLY LOST */
possibly lost: 16 bytes in 1 blocks
int *global = malloc(20); /* variable globale */
int main() { return (0); } /* oubli de free(global) */
still reachable: 20 bytes in 1 blocks
All heap blocks were freed -- no leaks are possible
ERROR SUMMARY: 0 errors from 0 contexts
free() n'a pas été appelé.Accès à une adresse mémoire invalide — hors des limites d'un bloc, après un free, ou via un pointeur nul.
/* Cas 1 : lecture hors limites (buffer overflow) */
int *arr = malloc(5 * sizeof(int));
int x = arr[5]; /* index 5 hors des 5 éléments (0 à 4) ! */
==123== Invalid read of size 4
==123== at 0x401155: main (main.c:6)
==123== Address 0x5204054 is 0 bytes after a block of size 20 alloc'd
/* Cas 2 : écriture après free (use-after-free) */
free(arr);
arr[0] = 42; /* écriture sur mémoire libérée ! */
==123== Invalid write of size 4
==123== at 0x401160: main (main.c:9)
==123== Address 0x5204040 is 0 bytes inside a block of size 20 free'd
Lecture d'une variable dont la valeur n'a jamais été assignée. Valgrind détecte aussi quand une décision (if/while) dépend d'une valeur non initialisée.
int x; /* déclarée mais pas initialisée */
if (x > 0) /* lecture de garbage ! */
printf("positif\n");
==123== Conditional jump or move depends on uninitialised value(s)
==123== at 0x401140: main (main.c:4)
/* Avec --track-origins=yes, Valgrind te dit où x a été créé */
==123== Uninitialised value was created by a stack allocation
==123== at 0x401130: main (main.c:2) ← ligne de déclaration
--track-origins=yes est crucial pour ce type d'erreur — sans lui, Valgrind signale où la valeur est utilisée, mais pas où elle a été créée./* Double free — libérer deux fois le même bloc */
int *p = malloc(40);
free(p);
free(p); /* ❌ */
==123== Invalid free() / delete / delete[] / realloc()
==123== at 0x484B27F: free (vg_replace_malloc.c:872)
==123== by 0x401155: main (main.c:5)
==123== Address 0x5204040 is 0 bytes inside a block of size 40 free'd
==123== at 0x484B27F: free (...)
==123== by 0x401150: main (main.c:4) ← premier free ici
/* Free sur une variable non-malloc (stack) */
int x = 5;
free(&x); /* ❌ x est sur la stack ! */
==123== Invalid free() / delete / delete[] / realloc()
==123== Address 0x... is on thread 1's stack
/* Mauvais appel de free (new/delete mismatch — plus courant en C++) */
/* En C — oubli du \0 dans un malloc de string */
char *s = malloc(5); /* 5 chars mais "hello" fait 6 avec \0 */
strcpy(s, "hello"); /* écrit hors limites ! */
==123== Invalid write of size 1
==123== at 0x...: strcpy (in .../mc_replace_strmem.c)
==123== by 0x401150: main (main.c:4)
==123== Address 0x5204045 is 0 bytes after a block of size 5 alloc'd
malloc(strlen(str)) au lieu de malloc(strlen(str) + 1). Valgrind le détecte avec "Invalid write of size 1".HEAP SUMMARY:
in use at exit: 72 bytes in 3 blocks ← mémoire non libérée en fin de prog
total heap usage: 5 allocs, 2 frees, 1,024 bytes allocated
↑ 5 malloc ↑ 2 free → 3 malloc non libérés → 3 leaks !
HEAP SUMMARY:
in use at exit: 40 bytes in 1 blocks
total heap usage: 3 allocs, 2 frees
LEAK SUMMARY:
definitely lost: 40 bytes
HEAP SUMMARY:
in use at exit: 0 bytes in 0 blocks
total heap usage: 3 allocs, 3 frees
All heap blocks were freed --
no leaks are possible
free() doit égaler le nombre de malloc(). Si "5 allocs, 3 frees" → 2 leaks garantis. Le nombre dans "in use at exit" doit être 0.Valgrind trace toutes les fonctions d'allocation — pas seulement malloc. Chaque appel compte dans le HEAP SUMMARY.
| Fonction | Comptée comme | Nécessite |
|---|---|---|
| malloc(size) | 1 alloc | 1 free() |
| calloc(n, size) | 1 alloc | 1 free() |
| realloc(ptr, size) | 1 alloc + 1 free implicite | 1 free() final |
| strdup(str) | 1 alloc (malloc interne) | 1 free() |
/* calloc — alloue ET initialise à 0 */
int *arr = calloc(5, sizeof(int));
/* Valgrind : 1 alloc de 20 bytes */
free(arr); /* ✅ 1 free → 0 leak */
/* realloc — le piège classique */
int *p = malloc(10 * sizeof(int));
p = realloc(p, 20 * sizeof(int)); /* ❌ SI realloc retourne NULL */
/* → p vaut NULL, l'ancien bloc est perdu → DEFINITELY LOST */
/* Fix — toujours utiliser un tmp avec realloc */
int *tmp = realloc(p, 20 * sizeof(int));
if (tmp == NULL) { free(p); return (NULL); } /* ✅ */
p = tmp;
/* Heap summary : "2 allocs, 2 frees" ← realloc = 1 free + 1 alloc */
/* Code bugué — free_list manquant */
node_t *head = NULL;
add_node(&head, 1);
add_node(&head, 2);
add_node(&head, 3);
return (0); /* ❌ oubli de free_list(head) */
/* Sortie Valgrind */
HEAP SUMMARY:
in use at exit: 48 bytes in 3 blocks
total heap usage: 3 allocs, 0 frees, 48 bytes allocated
LEAK SUMMARY:
definitely lost: 16 bytes in 1 blocks ← le head (perdu en 1er)
indirectly lost: 32 bytes in 2 blocks ← les 2 nœuds suivants
/* Fix : */
free_list(head);
return (0); /* ✅ */
/* Code bugué */
char *s = malloc(strlen("hello")); /* 5 bytes, mais besoin de 6 */
strcpy(s, "hello"); /* écrit le \0 hors limites */
==123== Invalid write of size 1
==123== at 0x...: strcpy (mc_replace_strmem.c)
==123== by 0x401150: main (main.c:5)
==123== Address is 0 bytes after a block of size 5 alloc'd
/* Fix : */
char *s = malloc(strlen("hello") + 1); /* ✅ +1 pour \0 */
/* Code bugué */
int result; /* non initialisée */
if (argc > 1)
result = atoi(argv[1]);
printf("%d\n", result); /* si argc == 1, result = garbage ! */
==123== Use of uninitialised value of size 4
==123== at 0x...: printf (...)
==123== by 0x401160: main (main.c:6)
==123== Uninitialised value was created by a stack allocation
==123== at 0x401130: main (main.c:2) ← ligne de déclaration
/* Fix : */
int result = 0; /* ✅ toujours initialiser */
typedef struct {
char *name; /* mallocé séparément */
int age;
} user_t;
user_t *u = malloc(sizeof(user_t));
u->name = malloc(20);
strcpy(u->name, "Alice");
free(u); /* ❌ oubli de free(u->name) avant ! */
==123== definitely lost: 20 bytes in 1 blocks ← u->name perdu
==123== at 0x401170: main (main.c:10)
/* Fix — toujours libérer dans l'ordre inverse */
free(u->name); /* ✅ 1. libérer les champs d'abord */
free(u); /* ✅ 2. libérer la struct ensuite */
/* Code bugué — realloc direct sans variable temporaire */
int *arr = malloc(5 * sizeof(int));
arr = realloc(arr, 10 * sizeof(int)); /* ❌ si realloc retourne NULL */
/* arr vaut NULL, l'ancien bloc est inaccessible → DEFINITELY LOST */
/* ET on ne peut même plus faire free(arr) car arr == NULL */
HEAP SUMMARY:
in use at exit: 20 bytes in 1 blocks ← l'ancien bloc perdu
LEAK SUMMARY:
definitely lost: 20 bytes in 1 blocks
/* Fix — TOUJOURS utiliser un tmp */
int *arr = malloc(5 * sizeof(int));
int *tmp = realloc(arr, 10 * sizeof(int));
if (tmp == NULL)
{
free(arr); /* ✅ arr est encore valide si realloc échoue */
return (NULL);
}
arr = tmp; /* ✅ seulement si succès */
free(arr);
Chaque bloc alloué doit être libéré exactement une fois. Ni deux fois (double free), ni zéro fois (leak).
malloc(strlen(str) + 1) — sans le +1, le \0 écrit hors limites et Valgrind le détecte.
Pour une struct avec des champs mallocés : libérer les champs d'abord, la struct en dernier. Pour une liste : libérer nœud par nœud, jamais juste le head.
Si malloc échoue au milieu d'une fonction, libérer ce qui a déjà été alloué avant de retourner NULL.
node_t *n = malloc(sizeof(node_t));
if (!n) return (NULL);
n->name = malloc(20);
if (!n->name) {
free(n); /* ✅ libérer ce qui est déjà alloué */
return (NULL);
}
Déclarer avec une valeur initiale : int x = 0;, char *p = NULL;. Valgrind détecte les lectures de variables non initialisées.
Un tableau de N éléments : indices valides de 0 à N-1. arr[N] est invalide — Valgrind le voit.
Les fuites mémoire, les accès invalides (invalid read/write), l'utilisation de valeurs non initialisées, les double free et les free invalides.
Sans -g, Valgrind affiche des adresses hexadécimales sans noms de fichiers ni numéros de lignes, rendant le rapport inutilisable pour déboguer.
"Definitely lost" : aucun pointeur ne pointe plus vers le bloc — irrécupérable. "Still reachable" : un pointeur existe encore mais free() n'a pas été appelé.
Trois appels à malloc/calloc/realloc ont été faits, mais seulement deux free() ont été appelés. Il reste 1 bloc non libéré. Selon si un pointeur pointe encore vers lui, ce sera "definitely lost" ou "still reachable".
All heap blocks were freed -- no leaks are possible ET ERROR SUMMARY: 0 errors from 0 contexts. Attention : certains "still reachable" liés aux buffers internes de stdio (printf, etc.) peuvent apparaître et sont tolérés par Holberton. Le "definitely lost" lui, est toujours inacceptable.
Il trace l'origine des valeurs non initialisées — Valgrind indique non seulement où la valeur est utilisée, mais aussi où la variable a été créée (déclarée sans initialisation). Sans cette option, tu sais qu'une valeur est non initialisée, mais pas où elle a été déclarée.
Lecture de 4 octets (un int) à une adresse invalide : hors des limites d'un malloc, après un free (use-after-free), ou via un pointeur NULL/non initialisé. Le "size N" indique la taille de l'accès : 1=char, 4=int, 8=pointeur/long.
Oui — il détecte aussi l'utilisation de variables locales non initialisées (stack), les lectures hors limites de tableaux locaux, et les accès invalides en général. La détection de mémoire ne se limite pas au heap.
Mémoire accessible seulement depuis un bloc "definitely lost". Par exemple, les nœuds d'une linked list dont le head est perdu. Corriger le "definitely lost" parent élimine automatiquement l'"indirectly lost".
Memcheck — c'est l'outil implicite quand tu lances valgrind ./prog. Les autres outils doivent être spécifiés avec --tool=. Les principaux : Callgrind (performance), Massif (usage heap), Helgrind (data races en multi-thread).
Helgrind détecte les data races dans les programmes multi-threadés — situations où deux threads accèdent à la même donnée sans synchronisation. On l'utilise avec valgrind --tool=helgrind ./prog. Différent de Memcheck qui lui se concentre sur la mémoire.
Valgrind retourne le code de sortie 1 si des erreurs sont détectées, au lieu du code de sortie du programme. Utilisé dans les scripts de vérification automatique (CI/CD) à Holberton pour qu'un projet "fail" automatiquement si Valgrind trouve des erreurs.
valgrind --leak-check=full --error-exitcode=1 ./prog
echo $? # → 1 si erreurs, 0 si clean
╔══════════════════════════════════════════════════════════════════╗
║ COMMANDE HOLBERTON STANDARD ║
╚══════════════════════════════════════════════════════════════════╝
gcc -g -Wall -Werror -Wextra main.c -o prog
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose ./prog
╔══════════════════════════════════════════════════════════════════╗
║ LES 4 TYPES DE LEAKS (du plus grave au moins grave) ║
╚══════════════════════════════════════════════════════════════════╝
definitely lost → plus aucun pointeur vers le bloc ← inacceptable !
indirectly lost → accessible via un "definitely lost" ← corriger le parent
possibly lost → pointeur vers l'intérieur d'un bloc ← rare en C simple
still reachable → pointeur existe mais pas de free() ← acceptable (mais éviter)
╔══════════════════════════════════════════════════════════════════╗
║ LES TYPES D'ERREURS MÉMOIRE ║
╚══════════════════════════════════════════════════════════════════╝
Invalid read/write of size N → accès hors limites ou après free
Use of uninitialised value → variable lue sans avoir été initialisée
Invalid free() → double free ou free d'une variable stack
Conditional jump on uninit... → if/while basé sur valeur non initialisée
╔══════════════════════════════════════════════════════════════════╗
║ RÉSULTAT PARFAIT ATTENDU PAR HOLBERTON ║
╚══════════════════════════════════════════════════════════════════╝
in use at exit: 0 bytes in 0 blocks
All heap blocks were freed -- no leaks are possible
ERROR SUMMARY: 0 errors from 0 contexts
╔══════════════════════════════════════════════════════════════════╗
║ RÈGLES D'OR ║
╚══════════════════════════════════════════════════════════════════╝
# ✅ 1 malloc = 1 free — sans exception
# ✅ malloc(strlen(s) + 1) pour les strings — toujours +1
# ✅ Libérer champs AVANT la struct
# ✅ realloc → toujours utiliser un tmp, jamais p = realloc(p, ...)
# ✅ Gérer les chemins d'erreur (free ce qui est déjà alloué)
# ✅ Initialiser toutes les variables à la déclaration
# ✅ Indices de 0 à N-1 — jamais arr[N]
# ✅ calloc/strdup/realloc comptent aussi comme des allocs
# ❌ Double free → crash + invalid free Valgrind
# ❌ Free sur variable locale (stack) → invalid free
# ❌ Lire/écrire après free → invalid read/write
# ❌ "definitely lost" JAMAIS acceptable — "still reachable" parfois toléré