HOWTO · C++

Évitez les fuites de mémoire en C++

Cet article explique comment éviter les fuites de mémoire en C++

Cet article présente plusieurs méthodes sur la façon d’éviter les fuites de mémoire dans C++.

Utilisez l’opérateur delete pour libérer chaque new bloc mémoire alloué

Les fuites de mémoire sont un problème courant pour les programmes qui interagissent directement avec l’interface de mémoire dynamique brute, ce qui oblige le programmeur à libérer chaque mémoire allouée avec la méthode correspondante. Il existe plusieurs interfaces pour la gestion de la mémoire accessibles en C++ ainsi que certains appels système spécifiques à la plate-forme, qui représentent essentiellement les fonctions de niveau le plus bas pouvant être utilisées à cette fin. Dans la plupart des cas, le programme régulier ne devrait utiliser que des fonctionnalités spécifiques à la langue comme les opérateurs new et delete pour la gestion manuelle de la mémoire ou utiliser des pointeurs intelligents pour la désallocation automatique de la mémoire.

La méthode la plus vulnérable aux fuites de mémoire consiste à utiliser les opérateurs new/delete pour gérer directement la mémoire du tas. Notez que chaque pointeur renvoyé par le new appel doit être passé à l’opérateur delete pour libérer les blocs de mémoire correspondants dans le système, sinon la mémoire pourrait s’épuiser. Par exemple, le programme suivant alloue un tableau char à 10 éléments et ne le libère pas avant la fin du programme. Ainsi, le programme souffrirait d’une fuite de mémoire.

#include <iostream>

using std::cout;
using std::endl;

constexpr int SIZE = 10;

int main() {
  char *arr = new char[SIZE];

  for (int i = 0; i < SIZE; ++i) {
    arr[i] = (char)(65 + i);
    cout << arr[i] << "; ";
  }
  cout << endl;

  return EXIT_SUCCESS;
}

Les fuites de mémoire peuvent être détectées à l’aide du programme valgrind, qui est un utilitaire en ligne de commande et peut être exécuté sur un fichier programme compilé. Notez que Valgrind est en fait un ensemble d’outils multiples, dont l’un se trouve être un utilitaire de vérification de la mémoire. La commande suivante peut être émise pour rechercher des fuites de mémoire.

valgrind --tool=memcheck --leak-check=full ./program

Une fois la vérification terminée, la sortie suivante est imprimée. Notez que, puisque nous n’avons pas appelé delete sur le pointeur arr, l’ensemble du tableau a entraîné une mémoire non libérée d’où l’enregistrement - definitely lost: 10 bytes dans le résumé de la fuite. Ce dernier est la taille réelle du tableau de caractères à 10 éléments.

==13732== HEAP SUMMARY:
==13732==     in use at exit: 10 bytes in 1 blocks
==13732==   total heap usage: 3 allocs, 2 frees, 73,738 bytes allocated
==13732==
==13732== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1
==13732==    at 0x483C583: operator new[](unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==13732==    by 0x1091FE: main (tmp.cpp:11)
==13732==
==13732== LEAK SUMMARY:
==13732==    definitely lost: 10 bytes in 1 blocks
==13732==    indirectly lost: 0 bytes in 0 blocks
==13732==      possibly lost: 0 bytes in 0 blocks
==13732==    still reachable: 0 bytes in 0 blocks
==13732==         suppressed: 0 bytes in 0 blocks
==13732==
==13732== For lists of detected and suppressed errors, rerun with: -s
==13732== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Utilisez std::unique_ptr pour libérer automatiquement les objets du tas

Le problème de code d’exemple précédent peut être résolu par le bouton delete [] arr; instruction avant que le programme ne se termine. Bien qu’une autre option consiste à utiliser l’un des pointeurs intelligents, par ex. std::unique_ptr. L’objet pointeur intelligent peut être initialisé avec le type donné et stocke le pointeur renvoyé par l’opérateur new. La seule différence avec le pointeur normal est que la région mémoire associée std::unique_ptr n’a pas besoin d’être delete explicitement. Au lieu de cela, le compilateur s’occupe de la désallocation une fois que l’objet sort de la portée.

#include <iostream>
#include <memory>

using std::cout;
using std::endl;

constexpr int SIZE = 10;

int main() {
  std::unique_ptr<char[]> arr(new char[SIZE]);

  for (int i = 0; i < SIZE; ++i) {
    arr[i] = (char)(65 + i);
    cout << arr[i] << "; ";
  }
  cout << endl;

  return EXIT_SUCCESS;
}