Chargement...

Notions d’adresses et de mémoire en C++

Apprenez les notions d’adresses et de mémoire en C++, ainsi que le stockage des données avec des exemples.

En C++, les données sont stockées dans la mémoire (RAM) de l’ordinateur. Chaque variable possède sa propre adresse en mémoire. Comprendre les adresses mémoire est essentiel pour maîtriser les fonctionnalités puissantes du C++ comme les pointeurs. Dans cet article, nous verrons la structure de la mémoire, le concept d’adresse et comment les variables sont stockées en mémoire.


1. Qu’est-ce que la mémoire ?

La mémoire (RAM) est l’espace où les données et les instructions sont temporairement stockées pendant l’exécution d’un programme. Lorsque le programme se termine, les données présentes dans la mémoire sont effacées. Le compilateur C++ réserve un espace mémoire pour chaque variable et lui attribue une adresse.

Par exemple, dans le code suivant, chaque variable possède une adresse unique :


#include <iostream>
using namespace std;

int main() {
    int age = 25;
    double pi = 3.14;

    cout << "Adresse de age : " << &age << endl;
    cout << "Adresse de pi : " << &pi << endl;

    return 0;
}

Exemple de sortie :


Adresse de age : 0x61ff0c
Adresse de pi : 0x61ff08

Chaque adresse représente une position physique dans la mémoire et est généralement affichée au format **hexadécimal**.


2. L’opérateur & (Adresse)

L’opérateur & renvoie l’adresse d’une variable — c’est-à-dire l’emplacement mémoire où sa valeur est stockée.


int nombre = 100;
cout << "Adresse de nombre : " << &nombre << endl;
cout << "Valeur de nombre : " << nombre << endl;

L’adresse est un emplacement, tandis que la valeur est la donnée stockée à cet endroit. Cette distinction est essentielle pour comprendre le concept de pointeur.


3. Structure de la mémoire (Stack et Heap)

Lorsqu'un programme C++ s'exécute, la mémoire est généralement divisée en deux zones principales :

ZoneDescriptionUtilisation
Stack Stocke les variables automatiques (locales). La mémoire est allouée lors de l'appel d'une fonction et libérée automatiquement lorsqu'elle se termine. Variables à durée de vie automatique (int, double, etc.)
Heap Utilisé pour la mémoire allouée dynamiquement. Sa gestion est effectuée manuellement avec new et delete. Structures de données dynamiques (objets créés avec new)

Remarque sur les performances : Le Stack est extrêmement rapide, car l'allocation de mémoire consiste simplement à déplacer un pointeur interne. Le Heap est plus lent, car le système d'exploitation doit rechercher puis réserver un bloc de mémoire libre correspondant à la taille demandée.

Exemple :

int x = 10;          // Stocké dans le Stack (rapide et automatique)
int *p = new int(5); // Stocké dans le Heap (plus lent, nécessite une libération manuelle)

Il est important de noter que le pointeur p lui-même est stocké dans le Stack, tandis que la valeur qu'il référence se trouve dans le Heap. Comprendre cette différence est essentiel pour éviter les fuites de mémoire (memory leaks).


4. L’opérateur sizeof

L’opérateur sizeof renvoie la taille (en octets) qu’occupe une variable ou un type de donnée dans la mémoire.


#include <iostream>
using namespace std;

int main() {
    cout << "Taille de int : " << sizeof(int) << " octets" << endl;
    cout << "Taille de double : " << sizeof(double) << " octets" << endl;
    cout << "Taille de char : " << sizeof(char) << " octets" << endl;
    return 0;
}

Valeurs typiques sur la plupart des systèmes :


5. Visualisation des variables en mémoire

Exemple :


int a = 5;
int b = 10;

Représentation en mémoire :


Adresse     Valeur
0x61ff0c → 5   (a)
0x61ff08 → 10  (b)

Chaque variable possède une adresse distincte. Ces adresses peuvent augmenter ou diminuer selon la disposition mémoire.


6. Introduction aux pointeurs

Un pointeur est une variable spéciale qui stocke l’adresse d’une autre variable. L’opérateur * (astérisque) est utilisé pour déclarer un pointeur.


#include <iostream>
using namespace std;

int main() {
    int nombre = 42;
    int *ptr = &nombre; // L’adresse de nombre est assignée à ptr

    cout << "Valeur de nombre : " << nombre << endl;
    cout << "Valeur pointée par ptr : " << *ptr << endl;
    cout << "Adresse contenue dans ptr : " << ptr << endl;

    return 0;
}

Dans cet exemple, ptr contient l’adresse de nombre. L’expression *ptr permet d’accéder à la valeur stockée à cette adresse.


7. Pointeur nul et utilisation sûre

Un pointeur ne doit jamais pointer vers une adresse invalide. S’il n’est pas encore initialisé, il doit recevoir la valeur nullptr.


int *p = nullptr;

if (p == nullptr) {
    cout << "Le pointeur ne pointe vers aucune adresse." << endl;
}

Cette vérification empêche les erreurs d’exécution et les segmentation faults.


8. Schéma mémoire avec pointeur

Exemple :


int x = 10;
int *p = &x;

Représentation mémoire :


Variable | Adresse  | Valeur
x        | 0x61ff0c | 10
p        | 0x61ff08 | 0x61ff0c

Le pointeur p stocke l’adresse de x. En utilisant *p, on accède à la valeur de x.


9. Gestion de la mémoire et sécurité

En C++, la gestion directe de la mémoire offre une grande flexibilité, mais elle implique également une responsabilité importante. Toute mémoire allouée dynamiquement doit être libérée à l'aide de delete.


int *p = new int(100); // Allouer de la mémoire sur le tas (heap)
cout << *p << endl;
delete p;              // Libérer la mémoire
p = nullptr;           // Éviter un pointeur suspendu (dangling pointer)
  

Une fuite de mémoire (memory leak) se produit lorsque la mémoire allouée n'est pas correctement libérée. Cela peut entraîner une consommation de mémoire de plus en plus importante et ralentir le programme au fil du temps. Pour éviter ce problème, chaque new doit être associé à un delete.

Comprendre les pointeurs bruts (raw pointers) et la gestion manuelle de la mémoire est essentiel pour apprendre le C++. Cependant, le C++ moderne (C++11 et versions ultérieures) déconseille leur utilisation dans le code de production. À la place, la bibliothèque standard fournit des Smart Pointers (tels que std::unique_ptr et std::shared_ptr, via l'en-tête <memory>). Ceux-ci gèrent automatiquement la libération de la mémoire, réduisant presque totalement les fuites de mémoire et les problèmes de pointeurs suspendus.


10. TL;DR

  • Chaque variable a une adresse mémoire (& pour l’obtenir).
  • Stack → mémoire automatique ; Heap → mémoire dynamique.
  • sizeof indique la taille d’un type.
  • int *p déclare un pointeur ; *p accède à la valeur pointée.
  • nullptr doit être utilisé comme valeur d’initialisation sûre.
  • La mémoire allouée avec new doit être libérée avec delete.
  • Tous les exemples peuvent être exécutés dans Visual Studio 2022 ou GCC 11+.

Articles connexes