Exploration des segments mémoire dans le langage C

L'organisation de la mémoire est un concept fondamental pour comprendre l'exécution des programmes C et C++. Cet article détaille les différents segments qui composent l'espace mémoire d'un processus.

Architecture mémoire d'un programme

Adresses hautes
┌─────────────────┐
│ Espace Noyau    │ ← Utilisé par le système d'exploitation
├─────────────────┤
│ Segment de pile │ ← Croît vers le bas
│   (Stack)       │
│                 │
├─────────────────┤
│ Bibliothèques   │ ← Objets partagés dynamiques
├─────────────────┤
│ Segment de tas  │ ← Croît vers le haut
│   (Heap)        │
├─────────────────┤
│ Segment BSS     │ ← Variables globales/statiques non initialisées
├─────────────────┤
│ Segment de données│ ← Variables globales/statiques initialisées
├─────────────────┤
│ Segment de code │ ← Instructions du programme (lecture seule)
└─────────────────┘
Adresses basses
  1. Segment de code (Text)

Cette zone stocke les instructions machine exécutables. Elle est typiquement en lecture seule pour prévenir toute modification accidentelle du code en cours d'exécution.

int additionner(int a, int b) {
    return a + b; // Code de la fonction
}

void appeler_fonction() {
    int resultat = additionner(5, 3); // L'appel est codé ici
}
  • Contenu : Code machine des fonctions
  • Propriétés : Lecture seule, partagebale entre processus, taille fixe
  1. Segment de données initialisées

Ce segment contient les variables globales et statiques qui possèdent une valeur d'initialisation explicite dans le code source.

int compteur_global = 100;
static int compteur_statique = 200;

void exemple() {
    static int compteur_local_statique = 300;
}
  1. Segment BSS (Block Started by Symbol)

Les variables globales et statiques non initialisées sont placées ici. Le système d'exploitation les initialise automatiquement à zéro au démarrage du programme. Cette section ne prend pas de place dans le fichier exécutable, seules ses dimensinos sont enregistrées.

int tableau_global_non_init[1000]; // → Segment BSS
static int var_statique_non_init; // → Segment BSS
  1. Segment de tas (Heap)

La mémoire allouée dynamiquement réside dans ce segment. La gestion de son cycle de vie est de la responsabilité du programmeur.

#include <stdlib.h>

void traiter_donnees() {
    int* mesures = (int*)malloc(100 * sizeof(int));
    char* tampon = calloc(256, sizeof(char));

    // ... utilisation ...

    free(mesures);
    free(tampon);
}
  1. Segment de pile (Stack)

La pile stocke les variables locales, les paramètres de fonction, l'adresse de retour et le contexte d'exécution. Sa gestion est automatique, basée sur les portées lexicales, et elle fonctionne selon le principe dernier entré, premier sorti (LIFO).

void calculer_moyenne(int notes[], int nombre) {
    int somme = 0; // Variable locale sur la pile
    for (int i = 0; i < nombre; i++) {
        somme += notes[i];
    }
    float moyenne = (float)somme / nombre; // Lorsque la fonction se termine, 'somme' et 'moyenne' sont libérées
}

Comparaison des segments

Segment Contenu typique Gestion Durée de vie
Code Instructions Compilateur/OS Durée du programme
Données Variables initialisées Compilateur Durée du programme
BSS Variables non initialisées OS (initialisation à zéro) Durée du programme
Tas Objets dynamiques Développeur (malloc/free) Manuelle
Pile Variables locales, contexte Système (basé sur les portées) Automatique (portée)

Considérations pratiques et erreurs courantes

Une mauvaise gestion de la mémoire entraîne des problèmes critiques :

  • Débordement de pile : Allouer de très grands tableaux sur la pile peut provoquer un plantage. ``` void risque_debordement() { int enorme_tableau[1048576]; // Probable dépassement de la taille de pile (souvent 8MB) }
  • Fuites mémoire : Oublier de libérer la mémoire allouée sur le tas. ``` void fuite_memoire() { int* donnees = malloc(1024); // Le programme se termine sans appeler free(donnees) }
  • Pointeur sauvage : Retourner l'adresse d'une variable locale. ``` int* fonction_dangereuse() { int valeur = 42; return &valeur; // Comportement indéfini : la mémoire n'est plus valide après le retour }
    
    

Bonne pratique : Vérifier le retour de malloc et définir les pointeurs à NULL après leur libération pour éviter les usages incorrects.

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

int main() {
    int var_globale_init = 10;
    int var_globale_bss;
    static int var_statique_init = 20;
    const int* chaine_constante = "Texte en .rodata";

    int* bloc_tas = malloc(sizeof(int) * 50);
    int var_locale_pile = 30;

    printf("Adresse d'une fonction (code) : %p\n", main);
    printf("Variable globale initialisée : %p\n", &var_globale_init);
    printf("Variable BSS : %p\n", &var_globale_bss);
    printf("Pointeur vers le tas : %p\n", bloc_tas);
    printf("Variable locale (pile) : %p\n", &var_locale_pile);

    free(bloc_tas);
    return 0;
}

Étiquettes: C mémoire segments pile tas

Publié le 30 mai à 20h25