Examen 1 - pointeurs et allocation dynamique Flashcards
Données (Data segment
pour les variables globales et statiques
Code (code segment)
pour le code compilé
Tas (Heap)
pour l’allocation dynamique
Pile (Stack)
pour les variables locales
void UneFonction (int nombre3);
int nombre1;
void main()
{
Ennemi * e1;
e1 = new Ennemi;
UneFonction(7);
}
void UneFonction(int nombre3)
{
int nombre2;
}
cptInstance
nombre1
nombre2
nombre3
e1 (pointeur)
Pile
Tas
Code
Données
Main()
e1
(contenu)
class Ennemi
{
static int cptInstance;
// . . .
};
cptinstance et nombre 1 données
rien code
e1 (contenu) tas
nombre2 nombre3 e1(pointeur) pile
(la taille d’un int)
4 octets
transtyper l’adresse de a (un int*) en…. pour pouvoir visualiser le contenu de la mémoire un octet à la fois
pointeur de caractères
(unsigned char*)
Passage par valeur
Supposons que l’on possède la déclaration de fonction suivante :
int doIt(int value){
{
return value+1;
}
Supposons que l’on fasse l’appel
int b = doIt(a);
En supposant que la variable value soit à l’emplacement mémoire 0x009efda8 on obtiendra lors
de l’appel de doIt le schéma mémoire suivant :
a
52 0 0 0
0x00effc08 0x00effc09 0x00effc0A 0x00effc0B
value
52 0 0 0
0x009efda8 0x009efda9 0x009efdaA 0x009efdaB
Nous obtiendrons deux emplacements mémoire distincts qui contiendront cependant les mêmes
valeurs
Passage par référence
Lorsque l’on passe un paramètre par référence, on utilise l’adresse de l’original. Une fa çon
d’obtenir l’adresse de l’original est d’utiliser l’opérateur &.
int a = 52;
int* ptrA = &a;
Si l’on veut accéder ce qui se trouve à une adresse on utilise l’opérateur *.
int valeurDeA = *ptrA;
// Évidemment ici valeurDeA et a auront la même valeur soit 52
ne fa çon
d’obtenir l’adresse de l’original
l’opérateur &
Si l’on veut accéder ce qui se trouve à une adresse
l’opérateur *
Si l’on exécute les instructions suivantes :
int a = 52;
int* ptrA = &52;
int* pValeur = ptrA + 1;
la valeur de pValeur sera celle de ptrA augmentée de 4 octets car ptrA est un pointeur de int
et un int occupe 4 octets en mémoire.
int* pData;
// Ne pointe vers RIEN DE CONCRET. On ne veut pas faire cela.
intialiser un pointeur
int* pData = new int;
est alors possible d’accéder au contenu de pData pour y placer des infos ou pour lire le contenu
std::cout «_space;*pData;
ou
*pData = 80;
Si vous tentez de lire le contenu d ’un emplacement mémoire AVANT d’y affecter une valeur,…
le
contenu est indéterminé
responsables de libérer la mémoire allouée avec new
lorsqu’elle n’est plus nécessaire.
delete pData; Le pointeur peut alors être réutilisé pour un autre emplacement mémoire mais le contenu
désormais pointé est indéterminé.
void doIt()
{
int * pData = new int;
pData = 80;
}
void main()
{
doIt();
}
A la fin de l’exécution de la fonction doIt, la portée de la variable local pData sera terminée. La
variable sera libérée MAIS PAS L’ESPACE MÉMOIRE POINTÉ par pData. Cela causera une fuite de
mémoire
chaque utilisation de new doit s’accompagner
d’un delete
correspondant.
une fonction ou une méthode qui alloue la mémoire
est
généralement responsable de la libérer. En programmation objet, l’objet qui allouera de la
mémoire sera responsable de la libérer lorsque sa portée prendra fin.
Allocation d’un tableau
int* pData = new int[10];
Pour accéder à un élément d’un tableau
std::cout «_space;pData[4];
est l’équivalent de
std ::cout «_space;*(pData+4);
Pour libérer un tableau
int* pData = new int[10];
// …
delete[] pData;
Vous comprenez maintenant pourquoi les tableaux sont indexés à partir de 0 et non de 1
C’est
parce que l’indice correspond au décalage par rapport à l’adresse du tableau.
int a = 5; //&a est
la référence (l’adresse) du premier
octet de la structure (ici, un entier)
int * p; //variable de type
adresse pouvant recueillir la
référence (adresse) d’un entier stocké en mémoire p = &a;
int * p = new int;
« allocation d’un bloc mémoire pouvant contenir un entier, la
variable p reçoit l’adresse du premier octet de ce bloc »
À ce stade-ci le bloc est créé en mémoire mais il contient une
donnée invalide (« junk »)
int * p = new int;
*p = 52;
On utilise l’opérateur « * »
pour affecter une valeur du type qui a été
déclaré
Statique
signifie que la mémoire pour ce tableau est allouée au moment de la compilation, pas dynamiquement pendant l’exécution.
Type des éléments (int *) pour un tab
Chaque élément du tableau est un pointeur vers un entier.
Cela signifie que chaque élément peut stocker l’adresse mémoire d’un entier.
initialiser un tab de pointeur
int* tab[10];
for (int i = 0; i < 10; i++) {
tab[i] = new int(0); // Alloue dynamiquement un int initialisé à 0
}
int * tab[10];
int a = 12;
tab[0] = &a;
Rien de dynamique dans cette approche, rien à faire non plus
lorsqu’on n’a plus besoin de ce tableau (tout est sur la stack).
Ne surtout pas faire de delete sur tab[0], il ne réfère pas à une
allocation dynamique (l’exécution va planter)
int * tab[10];
tab[0] = new int;
*tab[0] = 12 //même principe avec l’étoile
Cette fois-ci il y a un new: toutes les adresses stockées dans le
tableau vont pointer vers un bloc dynamique, il faut en faire la
suppression ! delete[] tab;
Ne fonctionne pas ici, cette instruction fonctionnait lorsqu’on avait
l’adresse du premier octet vers un tableau dynamique
supression tab de pointeur
for(int j = 0; j < 10; j++)
{
delete tab[ j ]; //suppression de tous les blocs dynamiques
}
Dans le cas d’une instance de classe, on veut accéder à des
méthodes ou propriétés de la classe
Sur une instance déclarée sur la stack,
nous connaissons déjà la
syntaxe avec l’opérateur d’accès « . » :
Ennemi e;
e.attaquer();
En allocation dynamique pour acceder methode
on utilise simplement l’opérateur
d’accès « -> »
Ennemi * ptrEnnemi = new Ennemi();
ptrEnnemi->attaquer();
La dernière instruction est l’équivalent de
*(ptrEnnemi).attaquer();
Si pour une raison quelconque ptrEnnemi est null ou invalide,
l’exécution va s’arrêter avec une erreur de mémoire
Destructeur
Le destructeur d’une classe (méthode spéciale préfixée du symbole
« ~ » est la dernière méthode appelée avant la suppression définitive
de l’instance en mémoire :
void attaquerEnnemi()
{
Ennemi e;
e.attaquer();
int a = 38;
[…]
} ou est appeler destructeur (exemple stack)
à la sortie de cette méthode, la stack est libérée de la variable
locale « e », le destructeur de la classe Ennemi sera appelé.
xemple (sur la heap) :
void attaquerEnnemi()
{
Ennemi * ptrEnnemi = new Ennemi();
ptrEnnemiattaquer();
delete ptrEnnemi; //Le destructeur de la classe
Ennemi est appelé ici;
int a = 38
[…]
}
you knooow
Ennemi * tabEnnemis = new Ennemi[10]; comment utiliser methode ?
tabEnnemis[0].attaquer();
Il faut garder en tête que ce ne sont pas des pointeurs
Ennemi * tabEnnemis[10]; comment utiliser methode ?
Cette fois-ci, on va stocker des adresses vers des instances de la
classe Ennemi
tabEnnemis[0]attaquer();
Cette fois-ci ce sont des pointeurs
Une classe garde la référence d’une autre classe dans une
de ses variables Composition de classes
Habituellement on ne touche pas à cette référence, on
laisse la stack faire son travail ! Composition faible
Composition de classes Une classe alloue dynamiquement une autre classe et
stocke l’adresse reçue dans une variable
Habituellement, la classe qui alloue la mémoire
s’occupe de la libérer Composition forte
Le mode par référence permet de
passer la valeur originale d’une variable en paramètre à une méthode.
En plus d’éviter de prendre une copie indépendante souvent inutile, le passage par référence permet
3
aussi à cette méthode de modifier le paramètre reçu et donc la valeur initiale. C’est un peu l’équivalent du
mot clé ref en C#.
utilité contructeur copie
Le problème est que les ennemies dans la classe héro ne sont pas copié en profondeur. La première cela
marche, mais ensuite lorsqu’il faut y ré-accéder le problème est que par défaut seul le premier ennemy est
copier car c’est là que pointe le pointeur.
N’oubliez pas d’utiliser le modificateur override même s’il n’est pas strictement obligatoire. Au
fait, pourquoi doit-on le mettre s’il n’est pas obligatoire?
On doit le mettre pour signaler au compilateur que c’est une méthode hérité. Ainsi il pourra mieux
nous aider. De plus, cela rend le code plus lisible.
int * ptrint1;
std::cout «_space;*ptrint1;
pas initialisé, erreur rien au bout du pointeur
int * ptrint2 = new int[3];
std::cout «_space;ptrint2[3] «_space;std::endl;
erreur, out of bound tableau 3 cases mais acces 4 eme element ?
int * ptrint1;
std::couts«ptrintint1;
memoire non initilalisé
int * ptrint4 = new int[10];
std::cout «_space;*ptrint4 «std::endl;
affiche valeur premier element
int * ptrint5 = new int[5];
for(int i = 0; i<5; i++){
ptrint5[i]=2*i;
}
std::cout«_space;ptrint5[2] «std::endl;
4
int * ptrint6 = new int[5];
for(int i = 0; i<5; i++){
ptrint6[i]=2*i;
}
std::cout«_space;*(ptrint5+2) «std::endl;
4
*ptab++
*ptab++ (post-incrémentation)
La valeur pointée par ptab est d'abord accédée. Ensuite, le pointeur ptab est incrémenté.
Effet :
La valeur retournée est celle de l'emplacement initial. Le pointeur ptab est modifié après avoir été déréférencé.
Exemple :
cpp
int tab[] = {1, 2, 3, 4, 5};
int *ptab = tab;
cout «_space;*ptab++; // Affiche 1
cout «_space;*ptab; // Affiche 2
*++ptab
int tab[] = {1, 2, 3, 4, 5};
int *ptab = tab;
cout «_space;*ptab; // Affiche 1
cout «_space;*++ptab; // Affiche 2
cout «_space;*ptab; // Affiche toujours 2
cout «_space;*++ptab; // Affiche 3 *++ptab (pré-incrémentation)
Le pointeur ptab est d'abord incrémenté. Ensuite, la valeur pointée par le nouveau ptab est accédée.
Effet :
Le pointeur ptab est modifié avant d'être déréférencé. La valeur retournée est celle du nouvel emplacement pointé.
vrai ou faux : pointeurs et references c la meme chose
faux, pointeur peut etre nul alors que une reference non, c’est un alias de la variable (varaible elle meme)
un pointeur peut changer de referer, la referance non, c tjrs la meme chose