Devoir Surveillé de Programmation Orientée Objets
Exercice n°1 - Trace d'exécution avec pointeurs et références Remarque préliminaire sur le code source : Le code extrait contient des erreurs de syntaxe (déclarations imbriquées, paramètres par défaut redéfinis).
D'après le document Devoir Surveillé de Programmation Orientée Objets
Cet article a été rédigé automatiquement à partir du document source, puis vérifié avant publication.

Document source
Programmation, Informatique · École Nationale des Sciences de l'Informatique (ENSI) · PDF · 6 pages · 2011
Afficher l'aperçu du document
Exercice n°1 - Trace d'exécution avec pointeurs et références
Remarque préliminaire sur le code source :
Le code extrait contient des erreurs de syntaxe (déclarations imbriquées, paramètres par défaut redéfinis). Pour pouvoir répondre à la question de manière logique, le code C++ a été reconstitué sous sa forme standard valide : les fonctions f1, f2, et f3 sont définies à l'extérieur de main(). La fonction f3 prend un argument par défaut int x = 7.
Une erreur critique (Comportement Indéfini) est présente dans f1 : elle retourne l'adresse d'une variable locale (res). Bien que la mémoire soit techniquement libérée à la fin de f1, nous ferons la trace en supposant que la zone mémoire n'est pas immédiatement écrasée par le compilateur, ce qui est l'exercice classique demandé dans ce type de devoir.
Tableau d'exécution
| Instruction | g1 |
ptr |
ptr1 |
a |
|---|---|---|---|---|
| État initial | 2 | non initialisé | non initialisé | non initialisé |
1. ptr=&g1; |
2 | adresse de g1 |
non initialisé | non initialisé |
2. int &a=*ptr; |
2 | adresse de g1 |
non initialisé | 2 (référence à g1) |
3. int *ptr1=f1(a); |
3 | adresse de g1 |
adresse de res (dangling) |
3 |
4. *ptr1=f2(ptr); |
12 | adresse de g1 |
adresse de res (valeur = 12) |
12 |
5. (*ptr1)++; |
12 | adresse de g1 |
adresse de res (valeur = 13) |
12 |
6. a++; |
13 | adresse de g1 |
adresse de res (valeur = 13) |
13 |
Détail du raisonnement
ptr=&g1;:ptrpointe désormais surg1.int &a=*ptr;:adevient une référence sur la variable pointée parptr, c'est-à-direg1. Toute modification deamodifierag1.int *ptr1=f1(a);: L'appelf1(a)passeg1par référence (x).res = ++x;pré-incrémenteg1qui passe à 3.resvaut 3. La fonction retourne l'adresse deres.ptr1pointe donc vers cette adresse mémoire devenue invalide.aetg1valent 3.*ptr1=f2(ptr);:- L'appel
f2(ptr)copie le pointeur.p = &g1; (*p)++;incrémenteg1, qui passe de 3 à 4.return *f3();appellef3(7)(car argument par défaut).- Dans
f3(7):x++fait passerxà 8.(*ptr)+=x;ajoute 8 àg1(carptrpointe surg1).g1= 4 + 8 = 12. La fonction retourneptr(référence sur pointeur). f2déréférence ce retour (*ptr), renvoyant donc une référence surg1(qui vaut 12).- L'affectation
*ptr1 = 12place la valeur 12 dans la case mémoire (invalide) pointée parptr1.
- L'appel
(*ptr1)++;: La valeur pointée parptr1passe à 13. Cela n'affecte pasg1.a++;:aétant une référence surg1,g1est incrémenté et passe à 13.
Exercice n°2 - Analyse de l'exécution et de la gestion mémoire
Pour analyser cet exercice, il faut garder à l'esprit l'ordre d'appel des constructeurs lors de l'instanciation (les attributs objets sont construits avant le corps du constructeur de la classe englobante) et la génération par défaut du constructeur de copie et de l'opérateur d'affectation si non redéfinis.
Trace de l'affichage produit et erreurs associées
A a1;
Affichage :
A::A()
A a2(1);
Affichage :
A::A(int) avec n=1
A a3=a1;
Affichage :
A::A(A&)0
(Note : Le constructeur de copie de A affiche la valeur de att, qui a été copiée depuis a1 et vaut 0).
a1=a2;
Affichage :
affectation d'un A avec 1
B b1(2);
Affichage :
A::A() (construction de l'attribut ab de b1 avant d'entrer dans le corps de B(int a))
B::B(2)
A::A(int) avec n=2 (création de l'objet temporaire A(2) lié à l'instruction ab = a)
affectation d'un A avec 2 (affectation de l'objet temporaire à l'attribut ab)
A::~A() (destruction de l'objet temporaire)
b1.afficher();
Affichage :
B::afficher()
A::A() (instanciation de l'objet local A a dans la méthode)
A::afficher(0)
A::~A() (destruction de l'objet local a à la fin de la méthode)
b1.get_att().afficher();
Affichage :
A::A(A&)2 (la méthode get_att retourne un A par valeur, ce qui invoque le constructeur de copie)
A::afficher(2)
A::~A() (destruction de l'objet temporaire retourné par la fonction)
B *b2;
Affichage : Aucun affichage.
Erreur : b2 est un pointeur non initialisé (dangling pointer).
b2->afficher();
Affichage :
B::afficher()
A::A()
A::afficher(0)
A::~A()
Erreur signalée : Violation de mémoire (Comportement Indéfini). On appelle une méthode sur un pointeur non initialisé. L'exécution ne plante pas immédiatement ici uniquement parce que B::afficher() n'accède à aucun attribut d'instance (le pointeur this n'est pas déréférencé dans la méthode), mais c'est une faute grave.
b2= new B(5);
Affichage :
A::A()
B::B(5)
A::A(int) avec n=5
affectation d'un A avec 5
A::~A()
*b2=a3 ;
Affichage :
A::A(A&)0 (L'objet a3 est converti implicitement en B via le constructeur B(A& t). Initialisation de l'attribut ab)
B::B(A&) avec t (corps du constructeur)
affectation d'un A avec 0 (L'affectation par défaut B::operator= appelle l'opérateur = de l'attribut A ab)
B::~B() (destruction du temporaire B créé pour la conversion)
A::~A() (destruction de l'attribut ab du temporaire)
Erreurs signalées :
- Fuite de mémoire : L'affectation par défaut
*b2 = objetTemporairefait une copie de surface (shallow copy) du pointeuri. Le pointeurb2->i(qui contenait une adresse valide issue denew B(5)) est écrasé. La mémoire allouée est perdue. - Violation de mémoire : Dans le constructeur de conversion
B(A& t), l'attributin'est pas alloué (new int). Lors de la destruction de l'objet temporaireB, le destructeur faitdelete i;sur un pointeur non initialisé.
b1=*b2 ;
Affichage :
affectation d'un A avec 0 (affectation de l'attribut ab par l'opérateur généré par défaut)
Erreur signalée : Fuite de mémoire. L'opérateur d'affectation par défaut écrase b1.i avec b2->i sans désallouer le pointeur initialement alloué dans b1(2).
B b3 = b1 ;
Affichage :
A::A(A&)0 (Constructeur de copie par défaut de B qui appelle le constructeur de copie de A pour ab)
Fin du bloc
Affichage et Erreurs : À la fin du bloc, les objets locaux sont détruits dans l'ordre inverse de leur création :
b3est détruit :B::~B(), puis crash probable (delete i;).b1est détruit :B::~B(), Double Libération de Mémoire (Double Free / Violation de mémoire).b1.ietb3.ipointent vers la même adresse (copie de surface),delete i;tentera de libérer une mémoire déjà libérée.A::~A().a3,a2,a1sont détruits :A::~A()appelé 3 fois.- Fuite de mémoire : L'objet pointé par
b2n'est jamais détruit car il n'y a pas dedelete b2;.
Exercice n°3 : Manipulation de Polynômes et Évaluation d'Expressions
Première partie : Classe Polynome
Le code est rédigé pour correspondre scrupuleusement aux demandes, avec des méthodes limitées à l'utilisation de +, -, et *.
Questions 1, 2, 3, 4, 5 et 6
#include <iostream>
using namespace std;
class Polynome {
private:
int* tab; // Tableau dynamique des coefficients
int N; // Degré du polynôme
public:
// 2) Constructeur avec paramètre par défaut
Polynome(int degre = 0) {
N = degre;
tab = new int[N + 1];
for (int i = 0; i <= N; i++) {
tab[i] = 0;
}
}
// Constructeur de copie (Indispensable quand on a des pointeurs,
// bien que non explicitement demandé, sans lui la pile plantera)
Polynome(const Polynome& p) {
N = p.N;
tab = new int[N + 1];
for (int i = 0; i <= N; i++) {
tab[i] = p.tab[i];
}
}
// Opérateur d'affectation (même remarque que le constructeur de copie)
Polynome& operator=(const Polynome& p) {
if (this != &p) {
delete[] tab;
N = p.N;
tab = new int[N + 1];
for (int i = 0; i <= N; i++) {
tab[i] = p.tab[i];
}
}
return *this;
}
// 4) Destructeur : Oui, il est utile et obligatoire.
// Justification : La classe alloue dynamiquement de la mémoire pour le tableau 'tab'.
~Polynome() {
delete[] tab;
}
// 3) Méthode d'initialisation
void initialiser() {
cout << "Entrez le degre du polynome : ";
cin >> N;
delete[] tab; // Nettoyer la mémoire précédente
tab = new int[N + 1];
for (int i = 0; i <= N; i++) {
cout << "Coefficient de x^" << i << " : ";
cin >> tab[i];
}
}
// 5) Opérateur d'addition
Polynome operator+(const Polynome& p) const {
int max_deg = (N > p.N) ? N : p.N;
Polynome res(max_deg);
for (int i = 0; i <= max_deg; i++) {
int coeff1 = (i <= N) ? tab[i] : 0;
int coeff2 = (i <= p.N) ? p.tab[i] : 0;
res.tab[i] = coeff1 + coeff2;
}
return res;
}
// 5) Opérateur de soustraction
Polynome operator-(const Polynome& p) const {
int max_deg = (N > p.N) ? N : p.N;
Polynome res(max_deg);
for (int i = 0; i <= max_deg; i++) {
int coeff1 = (i <= N) ? tab[i] : 0;
int coeff2 = (i <= p.N) ? p.tab[i] : 0;
res.tab[i] = coeff1 - coeff2;
}
return res;
}
// 5) Opérateur de multiplication
Polynome operator*(const Polynome& p) const {
Polynome res(N + p.N);
for (int i = 0; i <= N; i++) {
for (int j = 0; j <= p.N; j++) {
res.tab[i + j] += tab[i] * p.tab[j];
}
}
return res;
}
// 6) Affichage
void afficher() const {
for (int i = 0; i <= N; i++) {
cout << tab[i] << "*x^" << i;
if (i < N) {
cout << " + ";
}
}
cout << endl;
}
};
Deuxième partie : Classe Expression
Remarque sur l'énoncé :
L'énoncé montre dans le programme principal E= ABC *+ ;. En C++, cette syntaxe n'est pas valide (espaces sans délimiteurs de chaînes). L'opération d'affectation décrite consiste à parcourir l'expression. Nous allons donc surcharger operator= avec un const char* pour traiter la chaîne "ABC*+" et simuler l'association entre les lettres A, B, C et les objets du problème (en supposant, pour respecter la logique du texte, qu'on instancie des polynômes identifiés).
De plus, l'algorithme d'évaluation prescrit par le texte (dépiler un opérateur, dépiler deux polynômes, appliquer, ré-empiler, puis recommencer) suppose que les piles sont construites à l'envers ou qu'elles sont traitées différemment d'un analyseur postfixé classique. Nous l'implémenterons fidèlement à l'énoncé.
Question 7 : Fichier d'en-tête (Expression.h)
#ifndef EXPRESSION_H
#define EXPRESSION_H
#include "Polynome.h"
#include "Pile_oper.h"
#include "Pile_poly.h"
class Expression {
private:
Pile_poly operandes;
Pile_oper operateurs;
public:
Expression();
~Expression();
// Surcharge pour affecter une chaine de caractères représentant l'expression
Expression& operator=(const char* exp);
// Méthode d'évaluation
Polynome evaluer_poly();
};
#endif
Question 8 : Implémentation (Expression.cpp)
#include "Expression.h"
#include <iostream>
using namespace std;
// Constructeur et Destructeur : les piles s'instancient / se détruisent d'elles-mêmes
Expression::Expression() {}
Expression::~Expression() {}
// Implémentation de l'affectation
Expression& Expression::operator=(const char* exp) {
int i = 0;
while (exp[i] != '\0') {
if (exp[i] == '+' || exp[i] == '-' || exp[i] == '*') {
operateurs.empiler(exp[i]);
}
else if (exp[i] >= 'A' && exp[i] <= 'Z') {
// Note : L'exercice ne fournit pas le mécanisme de liaison (binding)
// entre la lettre 'A' et l'objet Polynome A défini dans le main.
// Dans une implémentation réelle de cet examen, on empilerait
// le polynôme correspondant. Ici, nous créons un polynôme fictif
// pour satisfaire le typage de la méthode empiler().
Polynome pTemp(1);
operandes.empiler(pTemp);
}
i++;
}
return *this;
}
// Implémentation de l'évaluation selon l'algorithme strict du sujet
Polynome Expression::evaluer_poly() {
while (!operateurs.estVide()) {
// Dépiler un opérateur de la pile des opérateurs
char op = operateurs.depiler();
// Dépiler les deux derniers polynômes stockés
Polynome p2 = operandes.depiler();
Polynome p1 = operandes.depiler();
Polynome res;
// Appliquer l'opérateur
if (op == '+') {
res = p1 + p2;
} else if (op == '-') {
res = p1 - p2;
} else if (op == '*') {
res = p1 * p2;
}
// Empiler le résultat
operandes.empiler(res);
}
// Une fois la pile vide, le polynôme résultat est dépilé et affiché
Polynome resultat_final = operandes.depiler();
resultat_final.afficher();
return resultat_final;
}
Méthode
Face à ce type de devoir surveillé, il faut faire preuve d'une double compétence :
- Comprendre l'architecture de la mémoire en C++ : L'exercice 1 et l'exercice 2 visent spécifiquement à tester votre capacité à tracer mentalement le cycle de vie d'un objet et l'évolution des pointeurs. Dessinez toujours les zones mémoires sur un brouillon (le tas et la pile) pour repérer les fuites de mémoire (allocations sans
delete), les comportements indéfinis (pointeur local détruit mais retourné) et les erreurs de copie de surface (absence de constructeur de copie profond provoquant un "double free"). - Suivre la spécification à la lettre : Dans l'exercice 3, l'énoncé fournit un algorithme d'évaluation qui s'écarte légèrement de l'algorithme classique d'évaluation postfixée. Dans le cadre d'un examen, même si l'énoncé vous paraît comporter une approximation (comme
E = ABC *+;qui n'est pas du C++ valide), vous devez produire le code qui se rapproche le plus de l'intention du professeur sans modifier fondamentalement sa classe. Implémentez rigoureusement les étapes numérotées du sujet.
Commentaires
Aucun commentaire pour le moment. Posez la première question.