Présentation de la mémoire des programmes C

26 Aug 2019 26 Aug 2019 7731 vues ESSADDOUKI Mostafa 10 min de lecture

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.

Définition — Espace mémoire d'un processus L'espace mémoire d'un programme C en cours d'exécution est divisé en zones spécialisées : le code exécutable, les données statiques (initialisées ou non), la pile pour les variables locales et les appels de fonctions, et le tas pour l'allocation dynamique.

Schéma — organisation mémoire d'un programme C : texte, données, BSS, tas, pile

#SegmentContenu principalModifiable ?Durée de vie
1Texte (code)Instructions machine compiléesNon (lecture seule)Toute l'exécution
2Données initialiséesVariables globales/statiques avec valeur initialeOui (partie RW)Toute l'exécution
3BSS (non initialisées)Variables globales/statiques sans valeur initialeOuiToute l'exécution
4Tas (heap)Mémoire allouée dynamiquement (malloc…)OuiJusqu'à free()
5Pile (stack)Variables locales, paramètres, adresses de retourOuiDurée de la fonction

1. Segment de texte (code)

Définition — Segment de texte Le segment de texte contient les instructions machine exécutables générées par le compilateur. Il est placé en bas de l'espace mémoire pour éviter d'être écrasé par le tas ou la pile.

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.
Contenu du segment de texte Ce segment contient le code binaire de toutes les fonctions du programme, y compris 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

Définition — Segment de données initialisées Ce segment contient les variables globales et les variables statiques auxquelles le programmeur a explicitement attribué une valeur initiale non nulle. Il est subdivisé en deux zones selon l'accès autorisé.
ZoneAccèsExemple de déclaration
Lecture / ÉcritureModifiable à l'exécutionint debug = 1; (global)
char s[] = "hello"; (global)
Lecture seuleNon modifiableconst 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;
}
Astuce — Variable 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)

Définition — Segment BSS Le segment BSS (Block Started by Symbol) contient les variables globales et statiques non explicitement initialisées ou initialisées à zéro. Le noyau les met à zéro automatiquement avant le démarrage du programme.

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;
}
Pourquoi un segment BSS séparé ? Stocker des milliers de zéros dans l'exécutable gaspillerait de l'espace disque. Le segment BSS n'occupe aucun espace dans le fichier exécutable — il ne contient que la taille à réserver. Le noyau effectue la mise à zéro au chargement du programme.
DéclarationSegmentValeur initiale
int a = 5; (global)Données initialisées (RW)5
int b; (global)BSS0 (mis par le noyau)
int c = 0; (global)BSS0
static int d = 3; (local)Données initialisées (RW)3
static int e; (local)BSS0
int f = 7; (local dans main)Pile7

4. La pile (stack)

Définition — Pile La pile est une zone mémoire de type LIFO (Last In, First Out) qui stocke les variables locales, les paramètres de fonctions et les adresses de retour. Elle croît vers les adresses basses sur x86 et est gérée automatiquement par le compilateur.

À 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 */
}
Récursivité et pile Chaque appel récursif crée un nouveau cadre de pile indépendant. Les variables de chaque niveau ne s'interfèrent pas. Cependant, des récursions trop profondes épuisent la pile et provoquent un stack overflow.
Danger — Stack overflow par récursion infinieUne fonction récursive sans condition d'arrêt consomme un nouveau cadre de pile à chaque appel jusqu'à épuisement :
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)

Définition — Tas (heap) Le tas est la zone mémoire utilisée pour l'allocation dynamique. Contrairement à la pile, la mémoire sur le tas n'est pas gérée automatiquement : le programmeur l'alloue avec malloc / calloc / realloc et doit la libérer explicitement avec free.

Syntaxe — Allocation sur le tas C
#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;
}
Sortie
tab[0] = 0
tab[1] = 10
tab[2] = 20
tab[3] = 30
tab[4] = 40
Danger — Fuite mémoire (memory leak) Oublier 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().
Attention — Double libération (double free) Appeler 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

AspectPile (stack)Tas (heap)
GestionAutomatique (compilateur)Manuelle (malloc / free)
VitesseTrès rapide (ajustement d'un pointeur)Plus lente (algorithme d'allocation)
TailleLimitée (quelques Mo selon l'OS)Grande (limité par la RAM disponible)
Durée de vieLimitée à la portée de la fonctionJusqu'à free() explicite
Types de variablesVariables locales, paramètresObjets de taille variable ou longue durée
RisquesStack overflow (récursion profonde)Fuite mémoire, double free, pointeur dangling
Direction de croissanceVers 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;
}
Sortie
globale   (données) : 0x601040
bss_var   (BSS)     : 0x601044
statique  (données) : 0x601048
locale    (pile)    : 0x7ffee4b2a4ac
dynamique (tas)     : 0x1b72010
Lecture des adresses Les adresses confirment la disposition en mémoire : les variables globales/statiques sont regroupées à des adresses basses (segment de données), les variables de la pile ont des adresses très élevées (zone haute), et le tas occupe une zone intermédiaire.

Récapitulatif

SegmentVariables stockéesDurée de vieGestion
TexteCode exécutable, littéraux de chaînesToute l'exécutionLecture seule — OS
Données initialiséesGlobales/statiques initialisées ≠ 0Toute l'exécutionAutomatique
BSSGlobales/statiques non initialisées ou = 0Toute l'exécutionMise à 0 par le noyau
PileLocales, paramètres, adresses de retourDurée de la fonctionAutomatique
TasMémoire allouée avec mallocJusqu'à free()Manuelle — programmeur

Discussion (0)

Soyez le premier à laisser un commentaire !

Laisser un commentaire

Votre commentaire sera visible après modération.