Recherche de site Web

C++ : Comprendre les pointeurs


Ce tutoriel s’adresse à tous les passionnés de programmation de tous niveaux qui souhaitent comprendre les pointeurs dans le langage C++. Tout le code présenté ici n’est pas spécifique au compilateur, et tous les exemples seront écrits en C++ ANSI simple. Les débats sur les pointeurs peuvent s’étendre sur des kilomètres, et il vous faudrait aller très loin pour tout maîtriser.

Si vous voulez vraiment aller aussi loin, cet article vous donne une compréhension claire des concepts fondamentaux des pointeurs et vous prépare à ce voyage. Cependant, ceux d’entre vous qui débutent dans la programmation C++ doivent s’assurer que vous êtes capable d’écrire et d’exécuter votre propre programme C++ « hello world », et il est également recommandé que vous ayez une compréhension de base des fonctions et des classes C++.

Si vous avez besoin de rafraîchir vos connaissances sur la compilation et l’exécution d’un programme C++, l’utilisation de fonctions et de classes, veuillez lire une annexe à la fin de ce document avant de poursuivre la lecture de cet article.

Dans ce tutoriel, vous allez apprendre :

  • Qu’est-ce qu’un pointeur C++ ?
  • Comment accéder à la valeur à l’adresse mémoire détenue par un pointeur
  • Comment manipuler des données à l’aide de pointeurs
  • Différences entre les tableaux et les pointeurs
  • Comment allouer et désallouer de la mémoire à l’aide de pointeurs

Qu’est-ce qu’un pointeur ?

Un pointeur est une variable qui stocke une adresse mémoire. D’accord, c’est assez simple, mais qu’est-ce qu’une adresse mémoire ? Chaque variable se trouve sous un emplacement unique dans la mémoire de l’ordinateur, et cet emplacement unique a sa propre adresse unique, l’adresse mémoire.

Normalement, les variables contiennent des valeurs telles que 5 ou « hello » et ces valeurs sont stockées sous un emplacement spécifique dans la mémoire de l’ordinateur. Cependant, un pointeur est une bête différente, car il contient l’adresse mémoire comme sa valeur et a la capacité de « pointer » (donc pointeur) vers une certaine valeur dans la mémoire, en utilisant son adresse mémoire associée.

Récupération de l’adresse mémoire d’une variable

Maintenant que vous avez une idée de base de ce qu’est un pointeur, voyons comment récupérer l’adresse mémoire d’une variable. Pour ce faire, nous devons utiliser l’opérateur address-of &.

#include <iostream>
int main()
{
  using namespace std;
// Declare an integer variable and initialize it with 99
  unsigned short int myInt = 99;
// Print out value of myInt
  cout << myInt << endl;
// Use address-of operator & to print out 
// a memory address of myInt
  cout << &myInt << endl;

return 0;
}

Sortie:


99
0xbff26312

La première ligne de la sortie contient une valeur entière de 99 et sur la deuxième ligne, une adresse mémoire de myInt est imprimée. Veuillez noter que votre production sera différente.

Attribution de l’adresse mémoire d’une variable à un pointeur

Avant de pouvoir attribuer une adresse mémoire à un pointeur, nous devons en déclarer un. La déclaration d’un pointeur en C++ est aussi simple que la déclaration de n’importe quelle autre variable, mais avec une seule différence. Un symbole d’astérisque * doit être ajouté et situé après le type de variable et avant le nom de la variable.

Une règle doit être suivie lors de l’attribution d’une adresse mémoire à un pointeur : le type de pointeur doit correspondre au type de variable vers lequel il va pointer. Une exception est un pointeur vers void, qui peut gérer différents types de variables vers lesquelles il pointe. Pour déclarer un pointeur pMark de type unsigned short int, la syntaxe suivante doit être utilisée :

#include <iostream>

int main()
{
  using namespace std;

// Declare and initialize a pointer.
  unsigned short int * pPointer = 0;
// Declare an integer variable and initialize it with 35698
  unsigned short int twoInt = 35698;
// Declare an integer variable and initialize it with 77
  unsigned short int oneInt = 77;
// Use address-of operator & to assign a memory address of twoInt to a pointer
  pPointer = &twoInt;
// Pointer pPointer now holds a memory address of twoInt

// Print out associated memory addresses and its values
  cout << "pPointer's memory address:\t\t" << &pPointer << endl;
  cout << "Integer's oneInt memory address:\t" << &oneInt << "\tInteger value:\t" << oneInt << endl;
  cout << "Integer's twoInt memory address:\t" << &twoInt << "\tInteger value:\t" << twoInt << endl;
  cout << "pPointer is pointing to memory address:\t" << pPointer << "\tInteger value:\t" << *pPointer << endl;

return 0;
}

Sortie:


pPointer's memory address:              0xbff43314
Integer's oneInt memory address:        0xbff43318      Integer value:  77
Integer's twoInt memory address:        0xbff4331a      Integer value:  35698
pPointer is pointing to memory address: 0xbff4331a      Integer value:  35698

Le diagramme ci-dessus est une abstraction visuelle de haut niveau de la façon dont les variables sont stockées dans la mémoire de l’ordinateur. Pointeur pPointer commence à l’adresse mémoire 0xbff43314 et prend 4 octets. Pointer pPointer contient, en valeur, une adresse mémoire d’un int twoInt court ( 2 octets ) qui est 0xbff4331a. Cette adresse est stockée sous forme de données binaires dans l’allocation d’espace mémoire du pointeur. Par conséquent, le déréférencement d’un pointeur avec une adresse mémoire 0xbff4331a accédera indirectement à la valeur de twoInt qui est dans ce cas un entier positif 36698.

Accès à la valeur de l’adresse mémoire détenue par un pointeur

Comme vous avez pu le voir dans l’exemple précédent, le pointeur pMark contient vraiment une adresse mémoire de valeur de oneInt. Un processus accédant à la valeur d’une variable par un pointeur est appelé indirection, car la valeur de la variable est accessible indirectement. La valeur de oneInt est désormais accessible indirectement à l’aide du pointeur pPointer.

Pour ce faire, nous devons déréférencer un pointeur avec l’opérateur de déréférencement *, qui doit être placé avant un nom de variable de pointeur :

#include <iostream>

int main()

{
  using namespace std;
// Declare an integer variable and initialize it with 99
  unsigned short int myInt = 99;
// Declare and initialize a pointer
  unsigned short int * pMark = 0;
// Print out a value of myInt
  cout << myInt << endl;
// Use address-of operator & to assign a memory address of myInt to a pointer
  pMark = &myInt;
// Dereference a pMark pointer with dereference operator * to access a value of myInt
  cout << *pMark << endl;

return 0;
}

Sortie:


99
99

Manipulation des données à l’aide de pointeurs

De la même manière que l’accès à la valeur à l’adresse mémoire détenue par le pointeur par l’indirection, l’indirection peut également être utilisée pour manipuler la valeur d’une variable. L’attribution d’une valeur à un pointeur déréférencé modifie indirectement la valeur d’une variable vers laquelle le pointeur pointe. L’exemple suivant illustre une manipulation simple de données à l’aide de pointeurs :

#include <iostream>

int main()

{
  using namespace std;
// Declare an integer variable and initialize it with 99
  unsigned short int myInt = 99;
// Declare and initialize a pointer
  unsigned short int * pMark = 0;
// Print out a value of myInt
  cout << myInt << endl;
// Use address-of operator & to assign memory address of myInt to a pointer
  pMark = &myInt;
// Dereference a pMark pointer with dereference operator * and set new value
  *pMark = 11;
// show indirectly a value of pMark and directly the value of myInt
  cout << "*pMark:\t" << *pMark << "\nmyInt:\t" << myInt << endl;

return 0;
}

Sortie:


99
*pMark: 11
myInt:  11

Le déréférencement du pointeur pMark et l’attribution d’une nouvelle valeur à 11 modifieront indirectement une valeur de myInt également à 11. Il est important de rappeler qu’un pointeur ne contient que l’adresse mémoire d’une variable, et non sa valeur.

Pour accéder à une valeur de la variable vers laquelle pointe un pointeur, le pointeur doit être déréférencé par l’opérateur de déréférencement *. Notez que * n’est pas une multiplication, par le contexte de votre code C++ votre compilateur différenciera si votre intention est d’utiliser l’opérateur de multiplication ou de déréférencement.

Pointeurs et tableaux en langage C++

En C++ , le nom d’un tableau est un pointeur constant vers son premier élément. La relation entre les pointeurs c++ et les tableaux est étroitement liée et son utilisation est presque interchangeable. Prenons l’exemple suivant :

#include <iostream>

int main()
{
  using namespace std;
// Declare an array with 10 elements
  int Marks [10]= {1,2,3,4,5,6,7,8,9,0};
// Print out the memory address of an array name 
  cout << Marks << endl;
// Print out the memory address of a first element of an array
  cout << &Marks[0] << endl;
// Print out value of the first element by dereferencing a array name
  cout << *Marks << endl;
return 0;
}

Sortie:


0xbf83d3fc
0xbf83d3fc
1

Comme vous pouvez le voir, un nom de tableau est en effet un pointeur vers son premier élément et il est donc parfaitement légal d’accéder aux éléments du tableau par un pointeur const. Par conséquent, le déréférencement d’un nom de tableau permettra d’accéder à une valeur du premier élément d’un tableau donné.

L’exemple suivant montrera comment accéder à d’autres éléments d’un tableau à l’aide de pointeurs const.

#include <iostream>

int main()
{
  using namespace std;

// Declare an array with 10 elements
  int Marks [10]= {1,2,3,4,5,6,7,8,9,0};
// Create a constant pointer to Marks array
  const int *pMarks = Marks;
// Access a 6th element of an array by pMarks pointer
  cout << *(pMarks + 5) << endl;
// Access a 6th element by dereferencing array name
  cout << *(Marks + 5) << endl;
// Access a 6th element of an array
  cout << Marks[5] << endl;

return 0;
}

Sortie:


6
6
6

Le 6ème élément d’un tableau peut être référencé avec une expression de pointeur *(Marks + 5). De plus, il est possible de déclarer un autre pointeur constant et de lui attribuer la valeur d’une adresse mémoire du premier élément d’un tableau. Ce pointeur se comportera alors de la même manière que le tableau Marks déclaré à l’origine.

Le signe + indique au compilateur de déplacer 5 objets qui sont des entiers à partir du début du tableau. Si l’objet, dans un entier, comme c’est le cas dans notre exemple, ( l’entier est généralement de 4 octets ), cela fera pointer le pointeur vers une adresse mémoire 20 octets derrière l’adresse réservée par le premier élément du tableau et pointant ainsi vers la 6ème.

L’exemple suivant illustre cette idée en détail :

#include <iostream>
int main()
{
  using namespace std;
// Declare an array with 10 elements
int Marks [10]= {1,2,3,4,5,6,7,8,9,0};
// Create a constant pointer to Marks array
const int *pMarks = Marks;

  for (int i=0, bytes=0; i < 10; ++i, bytes+=4)
  {
    cout <<  "Element " << i << ": " << pMarks << " + ";
    cout <<  bytes << " bytes" << " = " << (pMarks + i) << endl;
  }
return 0;
}

Sortie:


Element 0: 0xbfa5ce0c + 0 bytes = 0xbfa5ce0c
Element 1: 0xbfa5ce0c + 4 bytes = 0xbfa5ce10
Element 2: 0xbfa5ce0c + 8 bytes = 0xbfa5ce14
Element 3: 0xbfa5ce0c + 12 bytes = 0xbfa5ce18
Element 4: 0xbfa5ce0c + 16 bytes = 0xbfa5ce1c
Element 5: 0xbfa5ce0c + 20 bytes = 0xbfa5ce20
Element 6: 0xbfa5ce0c + 24 bytes = 0xbfa5ce24
Element 7: 0xbfa5ce0c + 28 bytes = 0xbfa5ce28
Element 8: 0xbfa5ce0c + 32 bytes = 0xbfa5ce2c
Element 9: 0xbfa5ce0c + 36 bytes = 0xbfa5ce30

Réfléchissez à l’opération arithmétique de pointeur suivante :

Comme nous savons que ce tableau particulier contient des entiers et que les entiers ont une longueur de 4 octets, la mémoire réservée pour l’ensemble du tableau est de 40 octets. L’adresse mémoire 0xbfa5ce0c est l’endroit où réside le premier élément du tableau. L’ajout de 20 octets ( 0x16 ) à une adresse du premier élément renverra l’adresse du 6ème élément : 0xbfa5ce0c + 0x16 ( 20 octets )=0xbfa5ce20.

Tableau de pointeurs

En C++, il est également possible de déclarer un tableau de pointeurs. Une telle structure de données pourrait être utilisée pour créer un tableau de chaînes ( tableau de chaînes ). En C++, une chaîne est en fait un pointeur vers son premier caractère, donc un tableau de chaînes serait essentiellement un tableau de pointeurs vers un premier caractère d’une chaîne dans l’élément de chaque tableau.

#include <iostream>
int main()
{
  using namespace std;

const char *linuxDistro[6] = 
	  { "Debian", "Ubuntu", "OpenSuse", "Fedora", "Linux Mint", "Mandriva"};

for ( int i=0; i < 6; i++) 
cout << linuxDistro[i] << endl;

return 0;
}

Sortie:


Debian
Ubuntu
OpenSuse
Fedora
Linux Mint
Mandriva

Pointeurs et tableaux

Cette section a été ajoutée pour clarifier la relation entre les pointeurs et les tableaux en C++ et elle peut être ignorée par un lecteur régulier. L’affirmation précédente selon laquelle les pointeurs et les tableaux sont très similaires et presque interchangeables est une bonne définition lorsqu’on s’efforce d’expliquer comment les pointeurs et les tableaux fonctionnent réellement en C++. En conséquence, la compréhension de ces similitudes élargit la connaissance du programmeur en C++.

Il y a beaucoup de discussions sur comment et dans quelle mesure les pointeurs et les tableaux sont liés en C++. Pour répondre à cette question, nous devons demander à Bjarne Stroustrup, le créateur du langage C++. Bjarne Stroustrup a écrit dans son livre The C++ Programming Language – Third Edition :

En C++, les pointeurs et les tableaux sont étroitement liés. Le nom d’un tableau peut être utilisé comme pointeur vers son élément initial. Le fait de prendre un pointeur vers l’élément un au-delà de la fin d’un tableau est garanti. C’est important pour de nombreux algorithmes. Cependant, comme un tel pointeur ne pointe pas en fait vers un élément du tableau, il ne peut pas être utilisé pour la lecture et l’écriture.

Le résultat de la prise de l’adresse de l’élément avant l’élément initial est indéfini et doit être évité. Sur certaines architectures de machine, les tableaux sont souvent alloués sur les limites d’adressage de la machine, donc « un avant l’élément initial » n’a tout simplement pas de sens.

Pourquoi avons-nous besoin de conseils ?

Jusqu’à présent, vous avez été exposé à la théorie et à la syntaxe derrière les pointeurs C++. Vous avez appris à attribuer une adresse mémoire d’une variable à un pointeur. L’attribution d’une adresse à un pointeur est utile pour expliquer le fonctionnement des pointeurs. Cependant, dans la vraie vie, vous ne feriez en aucun cas cela.

La bonne question à ce stade serait : Pourquoi avons-nous besoin de pointeurs en langage C++ si nous pouvons accéder et manipuler les variables en utilisant simplement leur nom de déclaration ? La capacité du C++ à accéder à la mémoire directement par des pointeurs rend le langage C++ favorable par rapport à d’autres langages tels que Visual Basic, C# ou Java.

L’accès aux variables directement par des pointeurs plutôt que par leur emplacement en mémoire permet d’augmenter l’efficacité et la flexibilité du code écrit. Cependant, comme on peut s’y attendre, l’augmentation de l’efficacité a un coût, car l’utilisation de tout outil de bas niveau tel qu’un pointeur signifie une difficulté accrue dans leur mise en œuvre.

L’utilisation la plus courante des pointeurs comprend :

  • Gestion des données sur le free store
  • Accès aux données et aux fonctions des membres de la classe
  • Passage de variables par référence à des fonctions

Où utilisons-nous les pointeurs ?

Dans cette section, nous allons explorer différentes façons d’utiliser les pointeurs dans le langage C++.

Attribution d’une mémoire à partir du « Free Store »

Les variables déclarées et utilisées localement à l’intérieur d’une définition de fonction sont détruites une fois qu’une valeur de retour est renvoyée à une instruction appelante. Cette approche de passage par valeur à une fonction présente l’avantage que le programmeur n’a pas à libérer une mémoire allouée par les variables locales de la fonction et que les variables passées à une fonction ne peuvent pas être modifiées.

L’inconvénient se produit lorsque les variables déclarées dans la portée d’une fonction doivent être utilisées par d’autres fonctions sans créer de surcharge en copiant ces variables via la valeur de retour de la fonction.

Une solution à ce problème consiste à créer des variables globales. Cependant, cela conduit toujours à une diminution de la lisibilité, de l’efficacité et des bogues du code, par conséquent, la déclaration de variables globales doit être évitée dans la mesure du possible. Les variables globales déclarées dans un espace de noms global détiennent une mémoire allouée pendant toute la durée d’exécution du programme et ne sont très souvent nécessaires que pendant une courte période.

Imaginons qu’un programmeur soit capable d’allouer dynamiquement une mémoire pendant l’exécution d’un programme. Cette allocation de mémoire peut également être utilisée n’importe où dans un code et libérée à tout moment n’est plus nécessaire. C’est là qu’intervient le soi-disant « Free Store ».

Le magasin libre est une mémoire disponible pour qu’un programmeur puisse être alloué et désalloué dynamiquement pendant l’exécution du programme.

Allocation d’une mémoire avec un nouvel opérateur

Le nouvel opérateur doit être utilisé lorsqu’il est nécessaire d’allouer une mémoire dans la mémoire libre. La valeur de retour d’un nouvel opérateur est un pointeur. Par conséquent, il ne doit être attribué qu’à un pointeur. En voici un exemple :

#include <iostream>
int main()
{
using namespace std;
// Delcare a pointer
unsigned short  * pPointer;
// allocate a memory with a new operator on the free store 
// and assign a memory address to a pointer named pPointer
pPointer = new unsigned short;
// assign an integer value to a pointer
*pPointer = 31;
// Print out the value of pPointer
cout << *pPointer << endl;

return 0;
}

Sortie:


31

La mémoire de stockage libre allouée pendant l’exécution du programme n’a été libérée qu’à la fin du programme réel. Pour ce programme court, c’est ok, mais ne pas désallouer une mémoire libre au système n’est pas une bonne pratique de programmation et plus important encore, votre code sera normalement composé de plus de 11 lignes.

Désallocation d’une mémoire à l’aide de l’opérateur delete

Pour renvoyer une mémoire allouée dans le magasin libre, un opérateur delete doit être utilisé :

#include <iostream>
int main()
{
using namespace std;
// Delcare a pointer
unsigned short * pPointer;
// allocate a memory with a new operator on the free store 
// and assign a memory address to a pointer named pPointer
pPointer = new unsigned short;
// assign an integer value to a pointer
*pPointer = 31;
// de-allocating memory back to the free store
delete pPointer;
// Print out a value of pPointer
cout << *pPointer << endl;

return 0;
}

Sortie:


0

L’opérateur delete libère une mémoire allouée par un nouvel opérateur et la renvoie dans le magasin libre. La déclaration du pointeur est toujours valide, de sorte que pPointer peut être réattribué. L’opérateur remove vient de supprimer l’adresse mémoire associée au pointeur.

NOTE
Il est important de mentionner que les pointeurs doivent toujours être initialisés même après l’opérateur de suppression. Ne pas le faire peut créer ce que l’on appelle un pointeur parasite, ce qui peut conduire à des résultats imprévisibles, car un pointeur parasite peut toujours pointer vers une ancienne position dans la mémoire et le compilateur peut utiliser cette adresse mémoire pour stocker d’autres données. Le programme peut fonctionner normalement. Cependant, une bombe à retardement pour faire planter le programme est réglée et le temps s’écoule.

Pointeurs de classe de type de données

La déclaration d’un pointeur de données de classe de type n’est pas différente de la déclaration d’un pointeur vers n’importe quel autre type de données. Considérez l’exemple suivant où un appareil de chauffage de classe simple sera construit pour démontrer ce concept.

#include <iostream>

using namespace std;
class Heater
{
// Declare public functions for class heater
public: 
// constrator to initialise temperature
    Heater( int itsTemperature);
// destructor, takes no action 
    ~Heater();
// accessor function to retrieve temperature
    int getTemperature() const;
// declare private data members
private:
    int temperature;
};
// definition of constractor
Heater::Heater(int itsTemperature)
{
    temperature = itsTemperature;
}
// definition of destructor
Heater::~Heater()
{
// no action taken here
}
// Definition of an accesor function which returns a value of a private Heater class member temperature
int Heater::getTemperature() const
{
return temperature;
}

int main()
{
// define a Heater class pointer object and initialize temperature to 8 by use of constructor
    Heater * modelXYZ = new Heater(8);
// Access an accessor function getTemperature() to retrieve temperature via an object modelXVZ
    cout << "Temperature of heater modelXYZ is :" << modelXYZ->getTemperature() << endl;
// Free an allocated memory back to a free store
    delete modelXYZ;

return 0;
}

Sortie:


Temperature of heater modelXYZ is :8

La plus grande partie du code ci-dessus est consacrée à une définition de classe. Le segment intéressant pour nous du point de vue des pointeurs se trouve dans la fonction principale. La mémoire d’un magasin libre est allouée à un nouvel objet pointeur modelXYZ de classe Heater.

Sur la ligne suivante de ce code, une fonction d’accesseur getTemperature() est appelée en référençant un objet pointeur avec un opérateur fléché. Enfin, une mémoire mise de côté d’un magasin libre est libérée. Il est important de mentionner que l’opérateur (.) qui est généralement utilisé pour accéder aux fonctions des membres de la classe ne peut pas être utilisé lorsqu’un objet est déclaré comme un pointeur, lorsqu’un objet de classe est déclaré comme un pointeur, un opérateur de flèche ( -> ) doit être utilisé à la place.

Passage à une fonction par référence à l’aide de pointeurs

En plus du passage par valeur et référence, le passage d’arguments à une fonction peut également être effectué à l’aide de pointeurs. Prenons l’exemple suivant :

#include <iostream>

using namespace std;

// FUNCTION PROTOTYPE
// passing to a function by reference using pointers
void addone( int * a, int * b);

int main()
{
// define integers a and b
int a = 1;
int b = 4;

  cout << "a = " << a << "\tb = " << b << endl;
// addone() function call
  cout << "addone() function call\n";
  addone(&a,&b);
// after addone() function call value a=2 and b=5 
  cout << "a = " << a << "\tb = " << b << endl;

return 0;
}

// addone() fuction header
void addone( int * a, int * b)
{
// addone() function definition
// this function adds 1 to each value
  ++*a;
  ++*b;
}

Sortie:


a = 1   b = 4
addone() function call
a = 2   b = 5

Le prototype de fonction de la fonction addone inclut des pointeurs en tant que types de variables acceptés. addone() ajoute 1 à chaque variable à laquelle une adresse mémoire a été passée lors de l’appel de la fonction, à partir de la fonction principale. Il est impératif de savoir que l’utilisation d’une référence à des pointeurs comme arguments de passage permet de modifier les valeurs impliquées également en dehors de la portée de la fonction addone().

C’est tout le contraire d’une situation où un programmeur passe à une fonction par valeur. Veuillez consulter une annexe à la fin de cet article, pour des exemples répertoriés sur la façon de passer à une fonction par valeur et par référence.

Fuites de mémoire associées aux pointeurs

Comme il a déjà été mentionné ci-dessus, l’utilisation de pointeurs et d’un nouvel opérateur donne à un programmeur un grand pouvoir. Un grand pouvoir s’accompagne très souvent d’une grande responsabilité. Une fausse manipulation avec une mémoire de stockage libre peut entraîner des fuites de mémoire. Ensuite, nous illustrerons quelques exemples de fuites de mémoire liées aux pointeurs C++.

Fuite de mémoire causée par la réaffectation du pointeur

La fuite de mémoire se produit lorsqu’une mémoire allouée à partir de la mémoire libre n’est plus nécessaire et n’est pas libérée par l’opérateur delete. Cela peut se produire en réaffectant un pointeur avant qu’un opérateur de suppression n’ait eu l’occasion de faire son travail :

#include <iostream>
int main()
{
using namespace std;
// Delcare a pointer
unsigned short * pPointer;
// allocate a memory with a new operator on the free store 
// and assign a memory address to a pointer named pPointer
pPointer = new unsigned short;
// assign an integer value to a pointer
*pPointer = 31;
// print out the value of pPointer and its associated memory address
cout << "*pPointer Value: " << *pPointer << "\tMemory Address: " << pPointer << endl;
// reassign a pointer to a new memory address from a free store
pPointer = new unsigned short;
// assign an integer value to a pointer
*pPointer = 15;
// print out a value of pPointer and its corresponding memory address
cout << "*pPointer Value: " << *pPointer << "\tMemory Address: " << pPointer << endl;
// de-allocating memory back to the free store
delete pPointer;

return 0;
}

Sortie:


*pPointer Value: 31     Memory Address: 0x90e9008
*pPointer Value: 15     Memory Address: 0x90e9018

Analyse de la sortie de l’exemple de la fuite de mémoire :

  • pPointer contient une adresse mémoire ( 0x90e9008 ) qui pointe vers une valeur entière 15.
  • pPointer a été réattribué avec une nouvelle adresse mémoire en allouant une nouvelle mémoire à partir d’un magasin libre.
  • L’adresse mémoire d’origine qui pointait vers une valeur 31 est perdue et, par conséquent, elle ne peut pas être libérée.
  • pPointer contient désormais une adresse mémoire ( 0x90e9018 ) qui pointe vers un entier de valeur 15.
  • La mémoire allouée avec une adresse 0x90e9018 est désallouée.
  • La mémoire allouée avec une adresse 0x90e9008 est libérée en raison de l’arrêt du programme.

Fuite de mémoire causée par une mauvaise utilisation des variables locales

Lorsqu’une fonction renvoie une valeur ou exécute sa dernière instruction, toutes les variables déclarées dans une définition de fonction sont détruites et ne sont plus accessibles à partir du segment de pile. Cela peut poser un problème quant à la mémoire d’un magasin libre allouée à un pointeur déclaré localement dans une définition de fonction, et elle n’est pas libérée avec l’opérateur delete avant de sortir de la portée.

L’exemple suivant illustrera comment cela peut se produire :

#include <iostream>
// function prototype
void localPointer();

int main()
{
// function call from the main function
localPointer();

return 0;
}

// function header
void localPointer()
{
// function definition
// variable declaration pPointer of type pointer 
unsigned short int * pPointer; 
// allocate a new memory from the free store and assign its memory address to a pointer pPointer 
pPointer = new unsigned short int;
// initialise pPointer
*pPointer = 38;
// print out a value to which pPointer is pointing to
std::cout << "Value of *pPointer : " << *pPointer << std::endl;
// de-allocation statement "delete pPointer;" is missing here

// function end
}

Sortie:


Value of *pPointer : 38

Lorsque la fonction localPointer() se termine, toutes les variables décalquées dans la portée d’une fonction sont supprimées de l’existence. pPointer est également une variable déclarée localement et, par conséquent, une adresse mémoire associée à la mémoire attribuée à partir de la mémoire libre sera perdue et donc, impossible de la désallouer.

Le programme s’exécutera normalement dans cet exemple simple. Cependant, à chaque fois que la fonction localPointer() est appelée, une nouvelle mémoire sera mise de côté et non libérée, ce qui diminuera l’efficacité du programme et provoquera le plantage du programme. D’autres exemples de fuites de mémoire peuvent être trouvés en ligne.

Conclusion

Comme il a déjà été mentionné précédemment, les pointeurs donnent un grand pouvoir à tout ingénieur logiciel qui les maîtrise. Cet article a essayé d’introduire des pointeurs de manière simple en utilisant des exemples, afin que le lecteur puisse être sur un bon moyen d’atteindre cet objectif.

L’accès direct à une mémoire par des pointeurs est l’une des meilleures caractéristiques du langage C++ et, pour cette raison, il est souvent préférable de le faire, également pour la programmation de périphériques embarqués. Une grande prudence doit être prise lorsque vous travaillez avec des pointeurs, car une grande puissance peut rapidement se transformer en désastre.

Appendice

Écrire, compiler et exécuter du C++ « hello world »

Code:

#include <iostream>

int main()
{
  using namespace std;
  cout << "Hello world\n";

return 0;
}

Compiler:


g++ hello-world.cc -o hello-world

Exécuter:


./hello-world

Sortie:


Hello world

Principes de base des fonctions C++

Veuillez noter que le programme suivant n’est fourni qu’à des fins de révision. Il ne doit pas servir de guide complet pour les fonctions C++.

#include <iostream>

using namespace std;

// FUNCTION PROTOTYPES

// passing to a function by value
int add( int a, int b);
// passing to a function by reference using pointers
void addone( int * a, int * b);
// passing to a function by reference using reference
void swap( int &a, int &b);

int main()

{
// define integers a and b
int a = 1;
int b = 4;

// add() function call return value will be printed out
  cout << "add() function\n";
  cout << add(a,b) << endl;
  cout << "a = " << a << "\tb = " << b << endl;
// addone() function call
  cout << "addone() function\n";
  addone(&a,&b);
// after addone() function call value a=2 and b=5 
  cout << "a = " << a << "\tb = " << b << endl;
// swap() function call
  cout << "swap() function\n";
  swap(a,b);
// after swap() function call value a=5 and b=2
  cout << "a = " << a << "\tb = " << b << endl;

return 0;
}
// add() fuction header
int add( int a, int b) 
{
// add() function definition
// this function returns sum of two integers
  return a + b;
}

// addone() fuction header
void addone( int * a, int * b)
{
// addone() function definition
// this function adds 1 to each value
  ++*a;
  ++*b;
}

// swap() function header
void swap( int &a, int &b)
{
// swap() function definition
// this function swaps values
int tmp;

  tmp = a;
  a = b;
  b = tmp;
}

Sortie:


add() function
5
a = 1   b = 4
addone() function
a = 2   b = 5
swap() function
a = 5   b = 2

Principes de base des cours C++

Veuillez noter que le programme suivant n’est fourni qu’à des fins de révision. Il ne doit pas servir de guide complet pour les classes C++.

#include <iostream>

using namespace std;
class Heater
{
// declare public functions for class heater
public: 
// constrator to initialize a private data member temperature
    Heater( int itsTemperature);
// destructor, takes no action 
    ~Heater();
// accessor function to retrieve a private date member temperature
    int getTemperature() const;
// declare private data members
private:
    int temperature;
};
// definition of constractor
Heater::Heater(int itsTemperature)
{
    temperature = itsTemperature;
}
// definition of destructor
Heater::~Heater()
{
// no action taken
}
// definition of accesor function which returns value of a private Heater class member temperature
int Heater::getTemperature() const
{
return temperature;
}

int main()
{
// define a Heater class object and initialise temperature to 8 by calling a constructor
    Heater modelXYZ(8);
// access accessor function getTemperature() to retrieve temperature via object modelXVZ
    cout << "Temperature of heater modelXYZ is :" << modelXYZ.getTemperature() << endl;
return 0;}

Sortie:


Temperature of heater modelXYZ is :8

Articles connexes: