Evita perdite di memoria in C++

Jinku Hu 12 ottobre 2023
  1. Usa l’operatore delete per liberare ogni new blocco di memoria allocato
  2. Usa std::unique_ptr per liberare automaticamente gli oggetti dell’heap
Evita perdite di memoria in C++

Questo articolo illustrerà più metodi su come evitare perdite di memoria in C++.

Usa l’operatore delete per liberare ogni new blocco di memoria allocato

Le perdite di memoria sono un problema comune per i programmi che interagiscono direttamente con l’interfaccia di memoria dinamica non elaborata, il che impone al programmatore l’onere di liberare ogni memoria allocata con il metodo corrispondente. Ci sono più interfacce per la gestione della memoria accessibili in C++ insieme ad alcune chiamate di sistema specifiche della piattaforma, che essenzialmente rappresentano le funzioni di livello più basso che possono essere utilizzate per questo scopo. Nella maggior parte dei casi, il programma normale dovrebbe utilizzare solo servizi specifici della lingua come gli operatori new e delete per la gestione manuale della memoria o utilizzare puntatori intelligenti per la deallocazione automatica della memoria.

Il metodo più vulnerabile per le perdite di memoria è utilizzare il new/delete operatori per gestire direttamente la memoria heap. Nota che ogni puntatore restituito dalla chiamata nuova dovrebbe essere passato all’operatore delete per rilasciare i blocchi di memoria corrispondenti al sistema, altrimenti la memoria potrebbe esaurirsi. Ad esempio, il seguente programma alloca un array char di 10 elementi e non lo libera prima dell’uscita dal programma. Pertanto, si dice che il programma soffra di una perdita di memoria.

#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;
}

Le perdite di memoria possono essere rilevate utilizzando il programma valgrind, che è un’utilità della riga di comando e può essere eseguita su un file di programma compilato. Notare che Valgrind è in realtà un insieme di più strumenti, uno dei quali è un’utilità di controllo della memoria. Il seguente comando può essere emesso per indagare sulle perdite di memoria.

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

Una volta completato il controllo, viene stampato il seguente output. Si noti che, poiché non abbiamo chiamato delete sul puntatore arr, l’intero array ha prodotto una memoria non liberata da cui il record - definitivamente perso: 10 byte nel riepilogo delle perdite. Quest’ultima è la dimensione effettiva dell’array di caratteri a 10 elementi.

==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)

Usa std::unique_ptr per liberare automaticamente gli oggetti dell’heap

Il precedente problema di codice di esempio può essere risolto con delete [] arr; prima che il programma termini. Tuttavia, un’altra opzione è utilizzare uno dei puntatori intelligenti, ad es. std::unique_ptr. L’oggetto puntatore intelligente può essere inizializzato con il tipo dato e memorizza il puntatore restituito dall’operatore new. L’unica differenza dal puntatore normale è che la regione di memoria associata a std::unique_ptr non ha bisogno di essere delete esplicitamente. Al contrario, il compilatore si occupa della deallocazione una volta che l’oggetto esce dall’ambito.

#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;
}
Autore: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.

LinkedIn Facebook

Articolo correlato - C++ Memory