2.3.1- Qu'est ce qu'un pointeur ?
2.3.2- Création dynamique d'objet
2.3.3- Dangers de la gestion directe de la mémorie
Un pointeur est une variable particulière, c'est une variable qui contient une adresse de variable dans la mémoire vive.
La mémoire vive est divisée en emplacemens d'un octet numérotés séquentiellement :
Numérotation des emplacements -> | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
---|---|---|---|---|---|---|---|
10100001 | 10111011 | 01010101 | 10110001 |
#include <iostream> int main() { int a; std::cout << &a; return 0; }
Pour obtenir l'adresse d'une variable, on utilise l'opérateur &. Soit le code suivant :
#includeint main() { int i=0; std::cout << "L'adress de i : " << &i; }
Soit la déclaration suivante :
int *a=0;
Ce code déclare a non pas comme une variable entière, mais comme un pointeur sur un entier de type int. Donc a recevra l'adresse d'une variable de type int. On peut tester la taille réservée à un pointeur en mémoire vive :
int *a; std::cout << "La taille pour socker un pointeur : " << sizeof(a);
On constate qu'un pointeur est stocké sur 4 octets. Noter : 24*8=168, cf le fait que l'on présente les adresses codées sur 8 hexadécimaux. Il est bon de déclarer les variables pointeurs en les indexant par p, et donc, pour l'exemple précédent, de déclarer int *pA; Ceci permet de distinguer les variables.
Comme un pointeur est une variable, il a un espace dédié en mémoire. Lors de sa déclaration, ce qui est stocké à cet espace est imprévisible. La valeur
de la variable, ie l'adresse stockée est donc imprévisible. On risque de produire une erreur grave en utilisant l'adresse stockée, par exemple en modifiant
le contenu stocké à cette adresse. En effet, l'information contenue à cette adresse peut être fondamentale pour le fonctionnement du système d'exploitation.
Pour éviter ce type de désagrement majeur, une bonne habitude consiste à affecter une valeur au pointeur dès sa déclaration.
Deux solutions pour affecter une valeur à un pointeur : soit on affecte le pointeur nul, soit on affecte l'adresse d'une variable.
double x=7; double *pteur1=0; double *pteur2=&x; std::cout << pteur1 << " et " << pteur2;
L'indirection consiste à accéder la valeur contenue à l'adresse enregistrée dans un pointeur. On parle aussi de déréferencement d'un pointeur. Dans le cas d'une variable standard, recupérer son contenu n'est pas difficile :
double y; double x=7; y=x;Dans ce qui précède, on récupère le contenu de x dans y. Pour accéder la valeur d'un pointeur :
double y; double x=7; double *pZ=&x; y=*pZ;
double x=5; double *pX=&x; *pX=7; cout << "La valeur de x : " << x;
double x=5; double *pX; pX=&x; *pX=7; cout << "La valeur de x : " << x;Noter que lorsque l'affectation d'un pointeur se fait en même temps que la déclaration, elle se fait double *pX=&x; alors que l'affectation du pointeur hors de la déclaration se fait : *pX=&x;
double x=5; double *pX; *pX=7; cout << "La valeur de x : " << x;Ce code provoquera une erreur à l'exécution. Précisement à l'exécution de *pX=7.
Les pointeurs sont utilisés :
Les usages seront développés et exemplifiés plus tard.
void methode1() { Action a(); int j; for(int i=0;i<7;i++) { Action a2(); int j2; } }Dans ce cas, les variables a et j sont locales à methode1() : c'est à dire qu'elle ne sont valables que dans le bloc d'instruction (l'espace entre les deux accolades) qui correspond à la définition de la fonction. Par ailleurs, les deux variables a2 et j2 sont locales au bloc d'instruction de la boucle. Par ailleurs, si on spécifie une fonction qui prend un objet comme paramètre :
void variation(Action a,int j) { .... }Dans ce cas, le passage des deux objets se fait par valeur : la machine crée un objet local à la fonction, de même qu'un entier local à la fonction. De sorte que l'objet modifié sera celui qui est local à la fonction. Pour que l'objet soit modifié, on va utiliser le passage de pointeur sur des objets. C'est à dire qu'on définira plutôt une fonction :
int main() { Action a; variation(&a,5); } void variation(Action *a,int j) { .... }Une autre solution va être de créer des objets de manière dynamique :
int main() { Action *a; a=new Action(); variation(a,5); } void variation(Action *a,int j) { .... }Dans le cas où les variables sont créées comme des variables locales à un bloc d'instruction, elles sont gérées dans la partie de la mémoire vive appellée la pile : dès que l'on sort du bloc d'instruction, la mémoire est libérée. en utilisant le mot clé new + constructeur, on crée un objet dans la partie de la mémoire vive qui est appellée tas. Dans l'exemple précédent, on crée un pointeur qui ne pointe sur rien au début, puis on demande à la machine de réserver de l'espace dans la mémoire libre, plus précisement un espace de taille Action et on affecte l'adresse de cet espace au pointeur a. Cette mémoire vive n'est pas liberée à la fin des blocs d'instructions : il faut gérer soi même la libération de la mémoire :
Action *a=new Action(); delete a;libère la mémoire vive dans le tas.
int i=0; int j=5; Action *a=new Action(i,j);
int *pInt; pInt=new int;Ou bien sûr :
int *pInt=new int;On a réservé une zone de mémoire du tas de taille int ici. delete permet là aussi de libérer de l'espace.
delete pInt;Note : en C, cela se faisait avec l'opérateur malloc pour memory allocation qui permettait d'allouer de la mémoire. Pour savoir quelles tailles sont réservées pour chaque type :
#includeclass A{ public : int k; double e; A(int i,int j); void doubleK(); }; A::A(int j,int a) { k=j; e=a; } void A::doubleK() { k=k*2; } int main() { A *varA=new A(5,7); std::cout << sizeof(A) << "\n"; std::cout << sizeof(int); }
varA->k=5; varA->doubleK(); int j=varA->k;
#includeNote, soit la classe :using namespace::std; class A { public : int num; ~A(); A(int j); donneNum(); }; A::A(int j) { num=j; cout << "\nAppel du constructeur de A pour l'objet de num : " << num; } A::donneNum() { cout << "\nLe chat de num " << num; } A::~A() { cout << "\nAppel du destructeur de A pour l'objet de num : " << num; } int main() { cout << "\nCreation de l'objet objA : dans la pile"; A objA(1); cout << "\nOn reserve un espace mémoire pour un objet de type A dans le tas"; A *pA=new A(5); delete pA; return 0; }
class B{ public : B(); ~B(); private : int *att1_B; int *att2_B; }; B::B() { att1_B=new int; att2_B=new int } B::~B() { delete att1_B; delete att2_B; }
#inlcudeclasse Rectangle{ public : Rectangle(); ~Rectangle(); void defLongueur(int longueur); int lireLongueur() const; void defLargeur(int largeur); int lireLargeur() const; private : int saLongueur; int saLargeur; }; Rectangle::Rectangle() { saLargeur=5; saLongueur=10; } Rectangle::~Rectangle() { } Rectangle::defLongueur(int l) { this->saLongueur=l; } Rectangle::defLargeur(int l) { this->saLargeur=l; } int main() { Rectangle theRect; cout << "theRect mesure " << theRect.lireLongueur() << " cm de long.\n"; cout << "theRect mesure " << theRect.lireLargeur() << " cm de largeur.\n"; theRect.defLongueur(20); theRect.defLargeur(10); cout << "theRect mesure " << theRect.lireLongueur() << " cm de long.\n"; cout << "theRect mesure " << theRect.lireLargeur() << " cm de largeur.\n"; return 0; }
Réaffecter une valeur à un pointeur alors que celui est l'unique référence d'un espace mémoire fait perdre la référence d'un espace mémoire que la machine considère comme affecté. Par exemple :
int *pX=new int; pX=new int;On a perdu la référence sur le premier espace mémoire en faisant cela, mais la machine ne considère pas cet espace comme disponible
#includeCe code ne provoque pas d'erreur : c'est qu'un tableau d'entiers est un pointeur sur entier. En créant un tableau de 5 entiers, on réserve 5 espaces de taille int en mémoire, on retient l'adresse du premier.int main() { int tab[5]; int *a=tab; }
#includeEn ajoutant 1 à un pointeur sur entier d'adresse x, on obtient un pointeur sur x+taille d'entier. On dispose donc de deux moyens pour parcourir le tableau :int main() { int tab[5]; int *a=tab; tab[0]=0; tab[1]=1; tab[2]=2; tab[3]=3; tab[4]=4; int temp=*(tab+1); std::cout << temp; }
#includeint main() { int tab[5]; int *a=tab; tab[0]=0; tab[1]=1; tab[2]=2; tab[3]=3; tab[4]=4; for(int i=0;i<5;i++) std::cout << *(tab+i) << " "; std::cout << "\n"; for(int i=0;i<5;i++) std::cout << tab[i] << " "; }
int *f1() { }
int **t;