Organisation de la mémoire en C
Quand un programme C est chargé et exécuté, le système d'exploitation lui alloue un espace mémoire structuré en cinq segments distincts, chacun ayant un rôle précis. Comprendre cette organisation est essentiel pour maîtriser la durée de vie des variables, les pointeurs et l'allocation dynamique.

| # | Segment | Contenu principal | Modifiable ? | Durée de vie |
|---|---|---|---|---|
| 1 | Texte (code) | Instructions machine compilées | Non (lecture seule) | Toute l'exécution |
| 2 | Données initialisées | Variables globales/statiques avec valeur initiale | Oui (partie RW) | Toute l'exécution |
| 3 | BSS (non initialisées) | Variables globales/statiques sans valeur initiale | Oui | Toute l'exécution |
| 4 | Tas (heap) | Mémoire allouée dynamiquement (malloc…) | Oui | Jusqu'à free() |
| 5 | Pile (stack) | Variables locales, paramètres, adresses de retour | Oui | Durée de la fonction |
1. Segment de texte (code)
Le segment de texte possède deux propriétés importantes :
- Partageable : plusieurs instances du même programme (p. ex. plusieurs terminaux ouverts) partagent une seule copie du segment de texte en mémoire.
- Lecture seule : le programme ne peut pas modifier ses propres instructions — toute tentative provoque une erreur de protection mémoire.
main(). Les littéraux de chaînes de caractères (ex. "hello") y sont souvent stockés également, dans la zone lecture seule — d'où l'erreur si l'on tente de les modifier via un char *.2. Segment de données initialisées
| Zone | Accès | Exemple de déclaration |
|---|---|---|
| Lecture / Écriture | Modifiable à l'exécution | int debug = 1; (global)char s[] = "hello"; (global) |
| Lecture seule | Non modifiable | const char *str = "hello"; (le littéral)Constantes globales const |
Exemple n°1 — Variables placées dans le segment de données
/* Toutes ces variables sont dans le segment de données initialisées */
int compteur = 10; /* zone lecture/écriture */
char ville[] = "Meknes"; /* zone lecture/écriture — tableau modifiable */
const char *msg = "bonjour"; /* pointeur en RW, littéral "bonjour" en RO */
int main(void)
{
static int appels = 1; /* variable statique locale — segment de données */
return 0;
}static locale Une variable locale déclarée static n'est passur la pile : elle est stockée dans le segment de données initialisées et conserve sa valeur entre les appels de la fonction.void compter()
{
static int n = 0; /* initialisée une seule fois */
n++;
printf("%d\n", n);
}
/* Appels successifs affichent : 1, 2, 3, … */3. Segment BSS (données non initialisées)
Exemple n°2 — Variables placées dans le segment BSS
int x; /* global, non initialisée → BSS, vaut 0 */
int y = 0; /* global, initialisée à 0 → BSS */
static int z; /* statique locale, non init → BSS, vaut 0 */
int main(void)
{
printf("x = %d, y = %d, z = %d\n", x, y, z);
/* Affiche : x = 0, y = 0, z = 0 */
return 0;
}| Déclaration | Segment | Valeur initiale |
|---|---|---|
int a = 5; (global) | Données initialisées (RW) | 5 |
int b; (global) | BSS | 0 (mis par le noyau) |
int c = 0; (global) | BSS | 0 |
static int d = 3; (local) | Données initialisées (RW) | 3 |
static int e; (local) | BSS | 0 |
int f = 7; (local dans main) | Pile | 7 |
4. La pile (stack)
À chaque appel de fonction, un cadre de pile (stack frame) est créé. Il contient :
- L'adresse de retour (pour reprendre l'exécution après le retour)
- Les paramètres de la fonction
- Les variables locales (variables automatiques)
- Les registres sauvegardés
Exemple n°3 — Cycle de vie d'un cadre de pile
#include <stdio.h>
int additionner(int a, int b) /* a et b : sur la pile, cadre de additionner */
{
int resultat = a + b; /* resultat : sur la pile */
return resultat; /* cadre détruit après retour */
}
int main(void)
{
int x = 3, y = 5; /* x, y : sur la pile, cadre de main */
int s = additionner(x, y); /* nouveau cadre créé pour additionner */
printf("s = %d\n", s);
return 0;
/* cadre de main détruit */
}void infini()
{
infini(); /* empile sans fin → Segmentation fault */
}
int main(void)
{
infini();
return 0;
}Toujours définir une condition d'arrêt dans une fonction récursive.5. Le tas (heap)
malloc / calloc / realloc et doit la libérer explicitement avec free.#include <stdlib.h>
void *malloc(size_t taille); /* alloue, contenu indéfini */
void *calloc(size_t n, size_t taille); /* alloue n éléments + met à 0 */
void *realloc(void *ptr, size_t n); /* redimensionne un bloc existant */
void free(void *ptr); /* libère le bloc alloué */Exemple n°4 — Allocation et libération sur le tas
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int n = 5;
/* Allocation de n entiers sur le tas */
int *tab = (int *)malloc(n * sizeof(int));
if (tab == NULL)
{
fprintf(stderr, "Erreur : allocation échouée\n");
return 1;
}
/* Utilisation */
for (int i = 0; i < n; i++)
tab[i] = i * 10;
for (int i = 0; i < n; i++)
printf("tab[%d] = %d\n", i, tab[i]);
/* Libération obligatoire */
free(tab);
tab = NULL; /* bonne pratique : évite un pointeur dangling */
return 0;
}tab[0] = 0 tab[1] = 10 tab[2] = 20 tab[3] = 30 tab[4] = 40
free() après malloc()laisse de la mémoire réservée inutilisable jusqu'à la fin du programme. Dans les programmes longs, cela provoque une consommation croissante de mémoire :void boucle()
{
for (int i = 0; i < 1000000; i++)
{
int *p = malloc(100); /* alloué… */
/* free(p) oublié → 100 octets × 1M = ~100 Mo perdus */
}
}Toujours faire correspondre un free() à chaque malloc().free() deux fois sur le même pointeur corrompt le gestionnaire de mémoire. Après un free(ptr), toujours mettre ptr = NULLpour éviter une utilisation accidentelle ultérieure :free(ptr);
ptr = NULL; /* free(NULL) est sans effet — sans danger */6. Pile vs Tas — Comparaison
| Aspect | Pile (stack) | Tas (heap) |
|---|---|---|
| Gestion | Automatique (compilateur) | Manuelle (malloc / free) |
| Vitesse | Très rapide (ajustement d'un pointeur) | Plus lente (algorithme d'allocation) |
| Taille | Limitée (quelques Mo selon l'OS) | Grande (limité par la RAM disponible) |
| Durée de vie | Limitée à la portée de la fonction | Jusqu'à free() explicite |
| Types de variables | Variables locales, paramètres | Objets de taille variable ou longue durée |
| Risques | Stack overflow (récursion profonde) | Fuite mémoire, double free, pointeur dangling |
| Direction de croissance | Vers les adresses basses (x86) | Vers les adresses hautes |
Exemple n°5 — Localisation de différentes variables en mémoire
#include <stdio.h>
#include <stdlib.h>
int globale = 42; /* segment données initialisées */
int bss_var; /* segment BSS */
int main(void)
{
int locale = 10; /* pile */
static int statique = 5; /* données init */
int *dynamique = malloc(sizeof(int)); /* tas */
*dynamique = 99;
printf("globale (données) : %p\n", (void *)&globale);
printf("bss_var (BSS) : %p\n", (void *)&bss_var);
printf("statique (données) : %p\n", (void *)&statique);
printf("locale (pile) : %p\n", (void *)&locale);
printf("dynamique (tas) : %p\n", (void *)dynamique);
free(dynamique);
return 0;
}globale (données) : 0x601040 bss_var (BSS) : 0x601044 statique (données) : 0x601048 locale (pile) : 0x7ffee4b2a4ac dynamique (tas) : 0x1b72010
Récapitulatif
| Segment | Variables stockées | Durée de vie | Gestion |
|---|---|---|---|
| Texte | Code exécutable, littéraux de chaînes | Toute l'exécution | Lecture seule — OS |
| Données initialisées | Globales/statiques initialisées ≠ 0 | Toute l'exécution | Automatique |
| BSS | Globales/statiques non initialisées ou = 0 | Toute l'exécution | Mise à 0 par le noyau |
| Pile | Locales, paramètres, adresses de retour | Durée de la fonction | Automatique |
| Tas | Mémoire allouée avec malloc | Jusqu'à free() | Manuelle — programmeur |
Discussion (0)
Soyez le premier à laisser un commentaire !
Laisser un commentaire
Votre commentaire sera visible après modération.