Exercices corrigés – Références et gestion mémoire en
C++
Stephan Tincho
Table des matières
1 Exercices sur les références 3
2 Exercices sur le passage de paramètres 5
3 Exercices sur l’allocation dynamique 6
4 Exercices de détection et correction d’erreurs mémoire 8
5 Exercice de synthèse : mini-vecteur dynamique 11
6 Exercices bonus – Smart pointers 13
1
Introduction
Ces exercices sont conçus pour vous aider à maîtriser les notions fondamentales de la
séance 2 :
— Les références (alias, règles, références const).
— Les pointeurs (rappels, différences avec les références).
— L’allocation dynamique avec new et delete (variables simples et tableaux).
— Le passage de paramètres (valeur, référence, référence constante).
— La détection et la correction des erreurs mémoire classiques.
— Une introduction au concept RAII et aux smart pointers.
Tous les exercices sont accompagnés d’un corrigé détaillé. Essayez de les résoudre par
vous-même avant de consulter les solutions.
2
1 Exercices sur les références
Exercice
1 – Comportement d’une référence Analysez le code suivant et prédisez l’affichage
avant de l’exécuter.
1 # include < iostream >
2 using namespace std ;
3
4 int main () {
5 int a = 10;
6 int b = 20;
7 int & ref = a ;
8
9 ref = 50;
10 cout << " a = " << a << " , b = " << b << " , ref = " << ref <<
endl ;
11
12 ref = b ;
13 cout << " a = " << a << " , b = " << b << " , ref = " << ref <<
endl ;
14
15 b = 99;
16 cout << " a = " << a << " , b = " << b << " , ref = " << ref <<
endl ;
17
18 return 0;
19 }
Questions :
1. Quelles sont les valeurs affichées ?
2. Après ref = b, ref devient-il un alias de b ? Justifiez.
3. Pourquoi la modification de b à 99 n’affecte-t-elle pas a ?
Corrigé
Affichage produit :
a=50, b=20, ref=50
a=20, b=20, ref=20
a=20, b=99, ref=20
Explications :
1. ref est un alias de a dès sa déclaration. ref = 50 modifie donc a qui devient
50.
2. ref = b ne change pas la liaison de ref : ref reste un alias de a. Cette
instruction copie la valeur de b (20) dans a (via ref). ref ne devient pas un
alias de b.
3. b est une variable indépendante. Modifier b n’a aucun effet sur a ni sur ref.
3
Exercice
2 – Référence constante Écrivez une fonction afficherMajuscule qui prend une
chaîne de caractères (std::string) par référence constante et l’affiche entièrement
en majuscules (utilisez toupper de <cctype>). Expliquez pourquoi la référence
constante est appropriée.
Corrigé
1 # include < iostream >
2 # include < string >
3 # include < cctype >
4
5 void afficherMajuscule ( const std :: string & texte ) {
6 for ( char c : texte ) {
7 std :: cout << static_cast < char >( std :: toupper ( c ) ) ;
8 }
9 std :: cout << std :: endl ;
10 }
11
12 int main () {
13 std :: string message = " Bonjour , monde ! " ;
14 afficherMajuscule ( message ) ; // Affiche " BONJOUR , MONDE !"
15 return 0;
16 }
La référence constante (const std::string&) évite une copie coûteuse de la chaîne
et garantit que la fonction ne peut pas modifier l’original.
4
2 Exercices sur le passage de paramètres
Exercice
3 – Fonction d’incrémentation Écrivez une fonction incrementer qui prend un
entier par référence et un pas (entier) et incrémente l’entier du pas donné. Testez-la
dans un main.
Corrigé
1 # include < iostream >
2 using namespace std ;
3
4 void incrementer ( int & valeur , int pas ) {
5 valeur += pas ;
6 }
7
8 int main () {
9 int compteur = 0;
10 incrementer ( compteur , 5) ;
11 cout << compteur << endl ; // Affiche 5
12 incrementer ( compteur , 3) ;
13 cout << compteur << endl ; // Affiche 8
14 return 0;
15 }
Exercice
4 – Échange de deux valeurs (swap) Écrivez une fonction swap qui échange le contenu
de deux entiers en utilisant des références. Testez-la.
Corrigé
1 # include < iostream >
2 using namespace std ;
3
4 void swap ( int & x , int & y ) {
5 int temp = x ;
6 x = y;
7 y = temp ;
8 }
9
10 int main () {
11 int a = 10 , b = 20;
12 swap (a , b ) ;
13 cout << " a = " << a << " , b = " << b << endl ; // a =20 , b =10
14 return 0;
15 }
5
3 Exercices sur l’allocation dynamique
Exercice
5 – Tableau de carrés parfaits Écrivez un programme qui :
1. Demande un entier n (entre 1 et 10).
2. Alloue dynamiquement un tableau de n entiers.
3. Remplit le tableau avec les n premiers carrés parfaits : 12 , 22 , 32 , . . . .
4. Affiche le tableau.
5. Libère correctement la mémoire.
Corrigé
1 # include < iostream >
2 using namespace std ;
3
4 int main () {
5 int n ;
6 cout << " n (1 -10) : " ;
7 cin >> n ;
8 if ( n < 1 || n > 10) {
9 cerr << " Valeur invalide " << endl ;
10 return 1;
11 }
12
13 int * carres = new int [ n ]; // Allocation
14
15 for ( int i = 0; i < n ; ++ i )
16 carres [ i ] = ( i +1) * ( i +1) ;
17
18 cout << " C a r r s parfaits : " ;
19 for ( int i = 0; i < n ; ++ i )
20 cout << carres [ i ] << " " ;
21 cout << endl ;
22
23 delete [] carres ; // L i b r a t i o n
24 carres = nullptr ;
25
26 return 0;
27 }
Exercice
6 – Allocation d’une chaîne dynamique Demandez à l’utilisateur de saisir une phrase,
allouez dynamiquement un tableau de caractères de la taille exacte nécessaire,
copiez-y la phrase et affichez-la. N’oubliez pas le caractère nul final. (Indice : utili-
sez strlen et strcpy ou mieux, std::string ; mais ici l’objectif est de manipuler
char*).
6
Corrigé
1 # include < iostream >
2 # include < cstring >
3 using namespace std ;
4
5 int main () {
6 char buffer [256];
7 cout << " Saisissez une phrase : " ;
8 cin . getline ( buffer , 256) ;
9
10 int longueur = strlen ( buffer ) + 1; // +1 pour ’\0 ’
11 char * phrase = new char [ longueur ];
12 strcpy ( phrase , buffer ) ;
13
14 cout << " Phrase stockee : " << phrase << endl ;
15
16 delete [] phrase ;
17 return 0;
18 }
7
4 Exercices de détection et correction d’erreurs mé-
moire
Exercice
7 – Code à corriger (4 erreurs) Le code suivant contient plusieurs erreurs de gestion
mémoire. Identifiez-les et proposez une correction.
1 # include < iostream >
2 using namespace std ;
3
4 void traitement ( int n ) {
5 int * tab ;
6 tab = new int [ n ];
7 for ( int i = 0; i < n ; i ++)
8 tab [ i ] = i * 10;
9 if ( n > 5) {
10 cout << " Trop grand ! " << endl ;
11 return ;
12 }
13 for ( int i = 0; i < n ; i ++)
14 cout << tab [ i ] << " " ;
15 cout << endl ;
16 delete tab ;
17 }
18
19 int main () {
20 int * p = new int (10) ;
21 cout << * p << endl ;
22 delete p ;
23 cout << * p << endl ;
24 return 0;
25 }
Corrigé
Erreurs identifiées :
1. int* tab; non initialisé (pas une erreur fatale ici car il est immédiatement
affecté, mais bonne pratique : le mettre à nullptr).
2. Dans le bloc if (n > 5), on return sans libérer le tableau → fuite mémoire.
3. delete tab; alors que le tableau a été alloué avec new[] → comportement
indéfini. Il faut delete[] tab;.
4. Dans main, après delete p, on accède à *p → use-after-free.
Code corrigé :
1 # include < iostream >
2 using namespace std ;
3
4 void traitement ( int n ) {
8
5 int * tab = nullptr ;
6 tab = new int [ n ];
7 for ( int i = 0; i < n ; i ++)
8 tab [ i ] = i * 10;
9 if ( n > 5) {
10 cout << " Trop grand ! " << endl ;
11 delete [] tab ; // L i b r a t i o n avant de quitter
12 return ;
13 }
14 for ( int i = 0; i < n ; i ++)
15 cout << tab [ i ] << " " ;
16 cout << endl ;
17 delete [] tab ;
18 tab = nullptr ;
19 }
20
21 int main () {
22 int * p = new int (10) ;
23 cout << * p << endl ;
24 delete p ;
25 p = nullptr ;
26 // cout << * p << endl ; // S u p p r i m ou d p l a c avant
delete
27 return 0;
28 }
Exercice
8 – Fuite mémoire et RAII Le code suivant provoque une fuite mémoire si une
exception est levée. Expliquez pourquoi et réécrivez-le en utilisant un unique_ptr
pour éviter la fuite.
1 void risquee () {
2 int * donnees = new int [1000];
3 // ... traitement qui peut lever une exception ...
4 delete [] donnees ;
5 }
Corrigé
Si une exception se produit entre new et delete[], la libération n’est jamais ef-
fectuée. Avec unique_ptr (ou vector), la mémoire est automatiquement libérée
lorsque l’objet sort de la portée.
1 # include < memory >
2 void risquee () {
3 std :: unique_ptr < int [] > donnees =
std :: make_unique < int [] >(1000) ;
4 // ... traitement , m m e avec exception , la m m o i r e sera
9
lib r e
5 }
Ou encore mieux, utiliser std::vector<int> donnees(1000);.
10
5 Exercice de synthèse : mini-vecteur dynamique
Exercice
9 – Classe VecteurDyn avec RAII Implémentez une classe VecteurDyn qui gère un
tableau d’entiers alloué dynamiquement. Elle devra respecter le principe RAII :
allocation dans le constructeur, libération dans le destructeur. Fournissez les mé-
thodes :
— VecteurDyn(int n) : constructeur allouant n entiers (initialisés à 0).
— VecteurDyn() : destructeur libérant la mémoire.
— void set(int index, int valeur) et int get(int index) const.
— int getTaille() const.
— void afficher() const.
Testez la classe dans un main.
Corrigé
1 # include < iostream >
2 using namespace std ;
3
4 class VecteurDyn {
5 private :
6 int * donnees ;
7 int taille ;
8 public :
9 VecteurDyn ( int n ) : taille ( n ) {
10 donnees = new int [ n ];
11 for ( int i = 0; i < n ; ++ i )
12 donnees [ i ] = 0;
13 }
14
15 ~ VecteurDyn () {
16 delete [] donnees ;
17 donnees = nullptr ;
18 }
19
20 void set ( int index , int valeur ) {
21 if ( index >= 0 && index < taille )
22 donnees [ index ] = valeur ;
23 }
24
25 int get ( int index ) const {
26 if ( index >= 0 && index < taille )
27 return donnees [ index ];
28 return -1; // Valeur sentinelle
29 }
30
31 int getTaille () const { return taille ; }
11
32
33 void afficher () const {
34 for ( int i = 0; i < taille ; ++ i )
35 cout << donnees [ i ] << " " ;
36 cout << endl ;
37 }
38 };
39
40 int main () {
41 VecteurDyn v (5) ;
42 for ( int i = 0; i < 5; ++ i )
43 v . set (i , ( i +1) *10) ;
44 v . afficher () ; // 10 20 30 40 50
45 cout << " Element 2 : " << v . get (2) << endl ; // 30
46 return 0; // Le destructeur l i b r e automatiquement
47 }
12
6 Exercices bonus – Smart pointers
Exercice
10 – Utilisation de unique_ptr Réécrivez l’exercice 5 (tableau de carrés parfaits)
en utilisant std::unique_ptr<int[]> au lieu de new[]/delete[].
Corrigé
1 # include < iostream >
2 # include < memory >
3 using namespace std ;
4
5 int main () {
6 int n ;
7 cout << " n (1 -10) : " ;
8 cin >> n ;
9 if ( n < 1 || n > 10) return 1;
10
11 unique_ptr < int [] > carres = make_unique < int [] >( n ) ;
12 for ( int i = 0; i < n ; ++ i )
13 carres [ i ] = ( i +1) *( i +1) ;
14
15 cout << " C a r r s : " ;
16 for ( int i = 0; i < n ; ++ i )
17 cout << carres [ i ] << " " ;
18 cout << endl ;
19 // Pas de delete explicite
20 return 0;
21 }
Synthèse des bonnes pratiques
— Toujours initialiser les pointeurs (nullptr).
— Pour chaque new doit correspondre un delete, pour chaque new[] un delete[].
— Mettre les pointeurs à nullptr après delete.
— Préférer les références aux pointeurs quand la valeur nulle n’est pas nécessaire.
— Pour les paramètres volumineux non modifiés, utiliser const T&.
— Utiliser les smart pointers (unique_ptr, shared_ptr) ou les conteneurs de la STL
(comme vector) plutôt que la gestion manuelle.
13