Gestion de la mémoire en C++

Salut ce forum apprends les bases du c++

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 114
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

Gestion de la mémoire en C++

Post by Hydraxx »

Gestion de la mémoire en C++

La gestion de la mémoire est fondamentale en C++. Contrairement à des langages qui cachent une grande partie de ce fonctionnement, C++ permet de manipuler directement des adresses, des pointeurs, la durée de vie des objets et des allocations dynamiques.

Pour le développement système, ces notions sont particulièrement importantes : buffers, structures d’API, échanges avec le système d’exploitation, parsing binaire, mémoire partagée et code bas niveau reposent constamment sur une bonne compréhension de la mémoire.

1. Modèle mémoire : pile et mémoire dynamique

Mémoire automatique

Une variable locale classique possède généralement une durée de vie automatique.

Code: Select all

void fonction()
{
    int valeur = 42;
}
La variable valeur existe pendant l’exécution de son bloc. Lorsque le bloc se termine, sa durée de vie se termine automatiquement.

On associe couramment ce fonctionnement à la pile (stack), même si le standard C++ décrit surtout des durées de stockage et ne garantit pas littéralement que toute variable automatique se trouve physiquement sur une pile.

Avantages :
  • gestion automatique ;
  • allocation très rapide en pratique ;
  • pas besoin de libération manuelle ;
  • durée de vie simple à comprendre.
Mémoire dynamique

Une allocation dynamique permet de créer une zone dont la durée de vie n’est pas directement limitée au bloc courant.

Code: Select all

int* ptr = new int(42);
ptr contient l’adresse de l’objet alloué.

Il faut distinguer deux choses :
  • ptr : la variable pointeur elle-même ;
  • *ptr : l’objet situé à l’adresse contenue dans ptr.
La variable ptr peut être locale alors que l’objet vers lequel elle pointe est dynamiquement alloué.

2. Adresses et pointeurs

Un pointeur est une variable contenant une adresse mémoire.

Code: Select all

int valeur = 42;
int* ptr = &valeur;
L’opérateur & permet ici d’obtenir l’adresse de valeur.

Code: Select all

&valeur
L’opérateur * appliqué à un pointeur permet de déréférencer celui-ci, c’est-à-dire d’accéder à l’objet désigné.

Code: Select all

*ptr = 100;
Cette instruction modifie valeur puisque ptr pointe vers valeur.

On peut se représenter la situation ainsi :

Code: Select all

ptr
 |
 v
+-------+
|  100  |   valeur
+-------+
Le pointeur n’est donc pas l’objet. Il indique où trouver l’objet.

nullptr

Un pointeur qui ne désigne aucun objet devrait généralement être initialisé avec nullptr.

Code: Select all

int* ptr = nullptr;
Il ne faut jamais déréférencer nullptr.

Code: Select all

*ptr = 5;
Ce code provoque un comportement indéfini.

3. Allocation avec new et libération avec delete

new crée dynamiquement un objet et retourne son adresse.

Code: Select all

int* ptr = new int;
On peut directement initialiser l’objet.

Code: Select all

int* ptr = new int(42);
On accède ensuite à sa valeur normalement.

Code: Select all

std::cout << *ptr;
Lorsque l’objet n’est plus nécessaire :

Code: Select all

delete ptr;
ptr = nullptr;
delete met fin à la durée de vie de l’objet et libère la mémoire correspondante.

Mettre ensuite le pointeur à nullptr évite de conserver volontairement une adresse devenue invalide.

Règle fondamentale

Une allocation effectuée avec new doit être libérée avec delete.

Code: Select all

int* ptr = new int(42);

delete ptr;
Ne pas libérer une allocation devenue inutile produit une fuite mémoire.

4. Tableaux dynamiques : new[] et delete[]

Il est possible d’allouer dynamiquement plusieurs objets.

Code: Select all

int* tableau = new int[100];
tableau désigne le premier élément de la zone.

On peut accéder aux éléments avec [].

Code: Select all

tableau[0] = 10;
tableau[1] = 20;
La libération doit être effectuée avec delete[].

Code: Select all

delete[] tableau;
tableau = nullptr;
Il faut respecter les couples :

Code: Select all

new      -> delete
new[]    -> delete[]
Mélanger les deux conduit à un comportement indéfini.

Pour un tableau d’objets, delete[] doit également permettre l’appel du destructeur de chacun des éléments concernés.

Dans du C++ applicatif moderne, un std::vector est généralement préférable à un tableau dynamique manuel lorsque l’objectif est simplement d’obtenir une collection redimensionnable. Cependant, comprendre new[] et delete[] reste indispensable pour comprendre la mémoire, les anciens codes et certaines interfaces bas niveau.

5. Tableaux multidimensionnels et disposition mémoire

Un tableau multidimensionnel classique peut être stocké dans une seule zone contiguë.

Code: Select all

int matrice[3][4];
Conceptuellement :

Code: Select all

ligne 0 : [0][0] [0][1] [0][2] [0][3]
ligne 1 : [1][0] [1][1] [1][2] [1][3]
ligne 2 : [2][0] [2][1] [2][2] [2][3]
Les éléments sont organisés séquentiellement en mémoire selon les règles du type tableau.

Une autre technique consiste à utiliser plusieurs allocations et des pointeurs.

Code: Select all

int** matrice = new int*[3];

for (int i = 0; i < 3; ++i)
    matrice[i] = new int[4];
Ici, les lignes sont des allocations distinctes. Elles ne sont donc pas obligatoirement adjacentes en mémoire.

La libération doit suivre la structure des allocations.

Code: Select all

for (int i = 0; i < 3; ++i)
    delete[] matrice[i];

delete[] matrice;
Cette différence est importante en bas niveau : une structure logique à deux dimensions n’implique pas forcément un unique bloc mémoire contigu.

6. Arithmétique des pointeurs

L’arithmétique des pointeurs permet de se déplacer entre les éléments d’un tableau.

Code: Select all

int tableau[4] = {10, 20, 30, 40};

int* ptr = tableau;
ptr pointe vers tableau[0].

Code: Select all

ptr + 1
désigne l’élément suivant, pas nécessairement l’adresse numérique précédente + 1 octet.

Le déplacement dépend du type pointé.

Pour un int* :

Code: Select all

ptr + 1
avance de sizeof(int) octets en mémoire sur une machine où cette représentation s’applique normalement.

On peut écrire :

Code: Select all

*(ptr + 2)
pour accéder au troisième élément.

C’est conceptuellement équivalent à :

Code: Select all

tableau[2]
L’arithmétique de pointeurs doit rester dans les limites définies pour le tableau concerné, avec l’exception particulière du pointeur situé juste après le dernier élément qui peut être formé pour les calculs/comparaisons mais ne doit pas être déréférencé.

7. Relation entre tableaux et pointeurs

Tableaux et pointeurs sont fortement liés, mais ils ne sont pas identiques.

Code: Select all

int tableau[4] = {1, 2, 3, 4};
Dans de nombreuses expressions, tableau est converti implicitement en pointeur vers son premier élément.

On parle souvent de array-to-pointer decay.

Ainsi :

Code: Select all

int* ptr = tableau;
est équivalent à récupérer l’adresse du premier élément.

Code: Select all

int* ptr = &tableau[0];
Mais le tableau lui-même reste un type tableau.

Par exemple :

Code: Select all

sizeof(tableau)
dans le même scope où tableau est réellement un tableau donne la taille du tableau complet.

Alors que :

Code: Select all

sizeof(ptr)
donne la taille du pointeur.

Passage à une fonction

Avec une déclaration telle que :

Code: Select all

void traiter(int tableau[])
le paramètre est traité comme un pointeur.

La fonction ne récupère donc pas automatiquement la longueur du tableau.

C’est pourquoi les interfaces bas niveau utilisent fréquemment le couple :

Code: Select all

pointeur + taille
Par exemple, conceptuellement :

Code: Select all

void traiter(const void* buffer, size_t taille);
Cette idée est omniprésente dans les API système.

8. Pointeurs vers pointeurs

Un pointeur peut lui-même être pointé par un autre pointeur.

Code: Select all

int valeur = 42;

int* ptr = &valeur;
int** pptr = &ptr;
Représentation :

Code: Select all

pptr -> ptr -> valeur
Pour obtenir valeur depuis pptr :

Code: Select all

**pptr
Ce mécanisme est fréquent dans les API qui doivent modifier un pointeur fourni par l’appelant ou retourner une adresse par paramètre de sortie.

9. Pointeurs invalides et dangling pointers

Un dangling pointer est un pointeur qui conserve l’adresse d’un objet dont la durée de vie est terminée.

Code: Select all

int* ptr = new int(42);

delete ptr;
Après delete, ptr contient encore potentiellement la représentation de l’ancienne adresse, mais l’objet n’existe plus.

Faire :

Code: Select all

std::cout << *ptr;
est un comportement indéfini.

On peut réduire certains risques en écrivant :

Code: Select all

delete ptr;
ptr = nullptr;
Mais cela ne corrige évidemment pas les éventuelles autres copies du pointeur qui désignaient la même ressource.

10. Opérations mémoire bas niveau

C++ permet également de manipuler directement des blocs de mémoire.

Des opérations classiques permettent notamment :
  • de copier des octets ;
  • de déplacer des octets ;
  • de remplir une zone ;
  • de comparer des zones.
Exemples d’outils C couramment rencontrés :

Code: Select all

memcpy
memmove
memset
memcmp
memcpy

Copie un nombre déterminé d’octets d’une zone source vers une zone destination.

Code: Select all

std::memcpy(destination, source, taille);
Il faut garantir que :
  • source contient suffisamment d’octets lisibles ;
  • destination contient suffisamment d’espace accessible ;
  • les objets concernés permettent ce type de copie brute ;
  • les zones ne se chevauchent pas d’une manière interdite par memcpy.
memmove

memmove sert à déplacer/copier des octets lorsque les zones peuvent se chevaucher.

memset

Remplit une zone mémoire octet par octet.

Code: Select all

std::memset(buffer, 0, taille);
Cette opération est très courante pour des buffers bruts, mais il ne faut pas supposer qu’elle constitue une méthode correcte d’initialisation pour n’importe quel objet C++ complexe.

memcmp

Compare le contenu brut de deux zones sur un nombre d’octets donné.

Comparer la représentation binaire de deux objets n’est pas toujours équivalent à comparer leur valeur logique, notamment à cause du padding et des représentations internes.

11. Gestion mémoire personnalisée

Une allocation dynamique générale possède un coût.

Dans certains programmes très sensibles aux performances, on peut vouloir contrôler précisément la manière dont les allocations sont effectuées.

Exemples :
  • moteurs de jeux ;
  • serveurs haute performance ;
  • systèmes embarqués ;
  • code temps réel ;
  • composants système.
Une technique consiste à réserver une grosse zone puis à servir de petites allocations depuis cette zone.

Principe :

Code: Select all

+------------------------------------------------+
| bloc réservé                                   |
+--------+--------+--------+--------+-------------+
| objet1 | objet2 | libre  | objet3 | libre ...  |
+--------+--------+--------+--------+-------------+
Cela permet notamment de réduire certaines allocations répétées et de mieux contrôler la disposition mémoire.

Object pools

Un object pool conserve des emplacements ou objets réutilisables.

Au lieu de détruire/allouer continuellement :

Code: Select all

créer -> utiliser -> détruire
créer -> utiliser -> détruire
on peut réutiliser :

Code: Select all

prendre dans le pool
utiliser
remettre dans le pool
Ce type d’optimisation n’est utile que lorsqu’un besoin réel a été identifié et mesuré.

12. Les erreurs mémoire les plus importantes

Fuite mémoire

Une fuite apparaît lorsqu’une ressource allouée n’est plus accessible mais n’a pas été libérée.

Code: Select all

void fuite()
{
    int* ptr = new int(42);
}
Lorsque la fonction se termine, ptr disparaît mais l’allocation n’a pas été libérée.

Dans un programme long, la répétition de telles fuites peut faire augmenter continuellement la consommation mémoire.

Accès hors limites

Code: Select all

int tableau[4];

tableau[10] = 42;
Le programme écrit en dehors du tableau.

C’est un comportement indéfini pouvant provoquer :
  • un crash ;
  • une corruption silencieuse ;
  • une modification d’autres données ;
  • une vulnérabilité de sécurité.
Sous-allocation d’un buffer

Une erreur classique consiste à réserver moins de mémoire que ce qui sera écrit.

Exemple conceptuel :

Code: Select all

char buffer[8];
puis tenter d’y placer davantage de données que sa capacité.

Le programme écrit alors hors du buffer.

Double libération

Code: Select all

int* ptr = new int;

delete ptr;
delete ptr;
La seconde opération est invalide.

Use-after-free

Code: Select all

int* ptr = new int(42);

delete ptr;

std::cout << *ptr;
Le programme utilise une ressource après sa libération.

Pointeur non initialisé

Code: Select all

int* ptr;

*ptr = 42;
ptr ne contient pas une adresse valide garantie.

C’est une erreur grave.

13. Pourquoi ces erreurs sont dangereuses

Les bugs mémoire sont particulièrement difficiles parce qu’une erreur peut ne pas provoquer immédiatement un crash.

Une écriture hors limites peut modifier une autre donnée et le crash apparaître beaucoup plus tard.

Exemple conceptuel :

Code: Select all

buffer A
buffer B
structure C
Un dépassement dans A peut altérer B ou C.

Le symptôme observé peut donc sembler totalement indépendant de l’endroit où l’erreur initiale s’est produite.

C’est une raison pour laquelle le debugging mémoire est une compétence importante en développement système.

14. Détection des fuites et erreurs mémoire

Sous Windows avec Visual C++, le CRT debug heap peut aider à détecter certaines fuites liées aux allocations qu’il suit.

On rencontre notamment les mécanismes de diagnostic du CRT permettant d’obtenir un rapport des allocations non libérées.

Sous Linux, Valgrind est un outil classique permettant de détecter notamment :
  • fuites ;
  • lectures invalides ;
  • écritures invalides ;
  • utilisations de mémoire libérée.
Les compilateurs modernes proposent également des sanitizers, notamment AddressSanitizer, très utile pour trouver :
  • buffer overflow ;
  • heap-use-after-free ;
  • stack-use-after-scope dans certains cas ;
  • double free et autres erreurs d’accès mémoire.
L’objectif important n’est pas seulement de connaître un outil : il faut apprendre à remonter du symptôme jusqu’à l’allocation et à la durée de vie responsables.

15. Garbage collection

Un garbage collector cherche automatiquement les objets qui ne sont plus accessibles afin de récupérer leur mémoire.

C++ ne repose pas par défaut sur un garbage collector obligatoire comme certains environnements managés.

Le C++ privilégie notamment :
  • durées de vie déterministes ;
  • destruction automatique des objets locaux ;
  • RAII ;
  • conteneurs ;
  • smart pointers lorsque l’ownership dynamique le justifie.
Cette approche donne un contrôle précis sur le moment où les ressources sont libérées.

16. Ownership et RAII

Une question centrale en gestion mémoire est :

Code: Select all

Qui possède cette ressource ?
Le propriétaire est responsable de sa durée de vie.

Avec un pointeur brut seul :

Code: Select all

int* ptr;
le type ne dit pas si ptr :
  • possède l’objet ;
  • observe simplement l’objet ;
  • doit le libérer ;
  • ne doit surtout pas le libérer.
Les smart pointers permettent d’exprimer certaines règles d’ownership directement dans le type.

Ils appliquent également le principe RAII : la durée de vie de la ressource est attachée à celle d’un objet C++ qui se charge de sa libération.

17. std::unique_ptr

std::unique_ptr représente généralement une propriété exclusive.

Code: Select all

std::unique_ptr<int> ptr = std::make_unique<int>(42);
Un seul unique_ptr possède normalement la ressource.

Lorsque ptr est détruit, la ressource est automatiquement libérée.

On peut généralement écrire plus simplement :

Code: Select all

auto ptr = std::make_unique<int>(42);
Pas de copie

Deux unique_ptr ne peuvent pas posséder indépendamment la même ressource via une simple copie.

Le code suivant est interdit :

Code: Select all

auto a = std::make_unique<int>(42);
auto b = a;
L’ownership peut en revanche être transféré.

Code: Select all

auto a = std::make_unique<int>(42);
auto b = std::move(a);
Après le déplacement, b possède la ressource et a ne la possède plus.

Cette sémantique permet d’exprimer clairement :

Code: Select all

cette ressource a exactement un propriétaire
18. Création avec make_unique

Pour créer un objet géré par unique_ptr, on privilégie généralement :

Code: Select all

auto ptr = std::make_unique<int>(42);
plutôt que :

Code: Select all

std::unique_ptr<int> ptr(new int(42));
make_unique rend l’intention explicite et évite d’exposer inutilement un new brut.

Pour un tableau :

Code: Select all

auto buffer = std::make_unique<std::byte[]>(1024);
La destruction utilisera correctement la forme adaptée au tableau.

19. Custom deleters

Un smart pointer n’est pas limité aux ressources qui doivent être libérées avec delete.

Il peut recevoir un destructeur personnalisé, appelé custom deleter.

C’est particulièrement intéressant en développement système.

Supposons qu’une bibliothèque C retourne une ressource qui doit être libérée par une fonction spécifique.

Conceptuellement :

Code: Select all

Resource* r = create_resource();
destroy_resource(r);
On peut associer destroy_resource à un smart pointer afin que la libération se fasse automatiquement lorsque l’objet propriétaire disparaît.

Le principe est :

Code: Select all

acquisition
    |
smart pointer
    |
fin de durée de vie
    |
fonction de libération appropriée
C’est une application de RAII à une ressource qui n’est pas nécessairement une allocation C++.

Le même concept peut servir avec des ressources système lorsqu’une abstraction C++ adaptée est souhaitée.

20. std::shared_ptr

std::shared_ptr représente une propriété partagée.

Code: Select all

auto a = std::make_shared<int>(42);
auto b = a;
a et b participent à la propriété du même objet.

Un compteur de références est maintenu dans un bloc de contrôle associé.

Conceptuellement :

Code: Select all

a ----+
      |
      +----> objet
      |
b ----+

compteur = 2
Si a disparaît :

Code: Select all

compteur = 1
L’objet reste vivant parce que b le possède encore.

Lorsque le dernier shared_ptr propriétaire disparaît :

Code: Select all

compteur = 0
la ressource gérée est détruite.

make_shared

On privilégie généralement :

Code: Select all

auto ptr = std::make_shared<Objet>();
make_shared peut notamment permettre une allocation combinée efficace pour l’objet et certaines données de contrôle.

Coût

shared_ptr est plus complexe que unique_ptr.

Il nécessite notamment la gestion du bloc de contrôle et du comptage de références.

Il ne faut donc pas utiliser shared_ptr simplement parce qu’il paraît plus pratique. Il sert lorsque la propriété est réellement partagée.

21. std::weak_ptr

weak_ptr observe une ressource gérée par shared_ptr sans devenir propriétaire de cette ressource.

Code: Select all

std::weak_ptr<int> faible;
On peut créer un weak_ptr depuis un shared_ptr.

Code: Select all

auto fort = std::make_shared<int>(42);
std::weak_ptr<int> faible = fort;
faible ne maintient pas l’objet vivant.

Pour essayer d’obtenir temporairement un shared_ptr valide :

Code: Select all

auto ptr = faible.lock();

if (ptr)
{
    std::cout << *ptr;
}
Si l’objet a déjà été détruit, lock() retourne un shared_ptr vide.

weak_ptr est notamment utile pour casser certains cycles de propriété.

Exemple problématique :

Code: Select all

A possède B
B possède A
Si les deux liens sont des shared_ptr, les compteurs peuvent empêcher la destruction des objets.

L’un des liens peut alors être non propriétaire avec weak_ptr.

22. Passage des smart pointers aux fonctions

Le type du paramètre doit refléter ce que la fonction veut faire.

Si une fonction doit prendre possession d’un unique_ptr, l’ownership peut être transféré.

Code: Select all

void prendre(std::unique_ptr<int> ptr);
Appel :

Code: Select all

prendre(std::move(ptr));
Après le transfert, l’appelant ne possède plus la ressource via ce unique_ptr.

Si la fonction veut seulement utiliser l’objet sans devenir propriétaire, il n’est pas toujours nécessaire de lui transmettre le smart pointer lui-même.

On peut par exemple travailler avec une référence ou un pointeur non propriétaire selon l’interface voulue.

Même principe pour shared_ptr : le passer par valeur signifie généralement que la fonction obtient une part de propriété pendant la durée correspondante.

L’ownership doit guider le choix du type.

23. Retour des smart pointers

Une fonction peut retourner un unique_ptr.

Code: Select all

std::unique_ptr<int> creer()
{
    return std::make_unique<int>(42);
}
Cela exprime clairement que la fonction crée une ressource et en transfère la propriété à l’appelant.

Un shared_ptr peut également être retourné lorsque le modèle de propriété doit réellement être partagé.

24. Interopérabilité avec les API C

Le développement système utilise énormément d’API basées sur :
  • pointeurs bruts ;
  • void* ;
  • buffers ;
  • paramètres de sortie ;
  • fonctions spécifiques de libération.
Les smart pointers ne font donc pas disparaître les pointeurs bruts.

Il faut comprendre les deux mondes.

Une API C peut avoir une logique conceptuelle de ce genre :

Code: Select all

Resource* create();
void destroy(Resource*);
Le programme C++ peut manipuler directement le pointeur brut ou créer une abstraction RAII avec un deleter approprié.

Dans du code bas niveau, la priorité reste de respecter exactement le contrat de l’API :
  • qui alloue ?
  • qui libère ?
  • avec quelle fonction ?
  • quelle est la taille du buffer ?
  • combien de temps le pointeur reste-t-il valide ?
  • l’API conserve-t-elle l’adresse après l’appel ?
Ce sont souvent ces questions qui déterminent si le code mémoire est correct.

25. out_ptr et inout_ptr

Certaines API C reçoivent l’adresse d’un pointeur afin d’y placer une ressource.

Conceptuellement :

Code: Select all

Resource** output
C++23 fournit std::out_ptr et std::inout_ptr pour faciliter certaines interactions entre smart pointers et API utilisant ce style de paramètres de sortie.

L’idée générale est d’adapter temporairement le smart pointer au format attendu par l’API, puis de lui faire reprendre correctement la propriété de la ressource retournée.

Il est surtout important de comprendre le problème sous-jacent : une API C peut vouloir modifier directement la valeur d’un pointeur appartenant à l’appelant.

26. Pointeurs bruts ou smart pointers ?

Un pointeur brut n’est pas mauvais en soi.

Il est indispensable dans de nombreux contextes bas niveau.

Code: Select all

void* buffer;
std::byte* data;
Structure* structure;
La vraie question est l’ownership.

Un raw pointer est très adapté pour :
  • observer une zone sans la posséder ;
  • parcourir un buffer ;
  • faire de l’arithmétique de pointeurs ;
  • interagir avec des API C/Win32/POSIX ;
  • manipuler des structures mémoire ;
  • travailler dans des environnements où la STL n’est pas appropriée.
Un smart pointer est particulièrement utile lorsqu’un objet C++ doit posséder une ressource dynamique et que sa libération peut être liée automatiquement à sa durée de vie.

Il faut donc connaître les pointeurs bruts en profondeur ET comprendre les abstractions modernes.

27. Ce qu’il faut retenir pour le développement bas niveau

Les points essentiels sont :
  • Un pointeur contient une adresse.
  • & obtient une adresse et * permet de déréférencer un pointeur.
  • La variable pointeur et l’objet pointé ont des durées de vie distinctes.
  • new/delete et new[]/delete[] doivent être correctement appariés.
  • Un tableau et un pointeur sont liés mais ne sont pas le même type.
  • Un tableau est souvent converti en pointeur vers son premier élément.
  • L’arithmétique des pointeurs dépend du type pointé.
  • Un buffer doit toujours être accompagné d’une connaissance fiable de sa taille.
  • Les accès hors limites provoquent un comportement indéfini.
  • Une allocation perdue provoque une fuite mémoire.
  • Utiliser une allocation après sa libération produit un use-after-free.
  • Libérer deux fois la même allocation est une erreur grave.
  • Les pointeurs invalides sont une source majeure de crashs et de vulnérabilités.
  • Les opérations mémoire brutes travaillent généralement en octets et exigent de connaître précisément la disposition des données.
  • Les allocations personnalisées et pools permettent de contrôler davantage les performances et la disposition mémoire.
  • unique_ptr exprime généralement un propriétaire unique.
  • shared_ptr exprime une propriété réellement partagée.
  • weak_ptr observe une ressource partagée sans la maintenir en vie.
  • Les custom deleters permettent d’appliquer RAII à des ressources qui utilisent une fonction de libération spécifique.
  • Les API système utilisent toujours énormément de pointeurs bruts : les smart pointers ne remplacent donc pas la compréhension de la mémoire.
  • Avant toute manipulation de ressource, demande-toi toujours : qui possède la ressource, quelle est sa taille, combien de temps est-elle valide et comment doit-elle être libérée ?
Pour du développement Win32, POSIX ou kernel, la compréhension des pointeurs, des tailles, des buffers, des durées de vie et des erreurs mémoire est plus fondamentale que la simple connaissance de la syntaxe C++.

Return to “Bases du C++”

Who is online

Users browsing this forum: No registered users and 1 guest