HOWTO · C++

C++ でメモリ リークを見つける

この記事では、C++ プログラミング言語でのメモリ リークについて説明します。

この記事では、メモリ リーク、その原因、特定方法、および C++ プログラミング言語を使用してそれらを防止する方法について説明します。

C++ でのメモリ リーク

プログラマーが以前に 1つの目的に割り当てたメモリの一部が別の目的で使用されている場合、メモリはリークされていると言われます。 このような事態が発生すると、プログラマはリソースの割り当てを正しく解除しません。

この時点で、プログラムはそのような RAM を使用する必要はありません。 その結果、その座席の予約を維持する理由はありません。

C++ では、プログラマが new キーワードを使用してメモリを割り当て、delete() 関数または delete[] 演算子を使用してメモリの割り当てを解除しないと、メモリ リークが発生します。 これにより、メモリが失われます。 ほとんどの場合、メモリ リークは正しくない delete 演算子によって引き起こされます。

delete[] 演算子は、配列内のデータを解放することがあります。

自動車のオイルを交換しても、同時にシールを交換したり、ネジを締めたりすることを怠ると、運転中に重大な問題が発生する可能性があります。

私たちの自動車のエンジンは、最終的にすべてのオイルが車から流出するため、最終的に焼き付きます。 C++ プログラミングでも、まったく同じ結果が得られます。

プログラマがメモリを動的に割り当てた後、そのメモリを解放しない場合、メモリ リークが発生し、メモリの割り当ては次のように行うことができます。

int *data;
data = (int *)malloc(8);
char *login = new char(40);

メモリを動的に割り当てる場合、そのメモリは C++ が利用するシステム メモリの一部であるヒープから取得されます。 スタックは、変数と関数が取得される場所です。

これらの割り当てをクリアするために次のコードを追加しないと、メモリ リークが発生します。 これらのリークは時間の経過とともに蓄積され、プログラム ロジックによっては、アプリケーションが失敗する可能性があります。

free(data);
delete login;

ポインターに 8 バイトのヒープ領域を割り当てる関数を作成する別のケースを次に示します。 これは、64 ビット コンピュータで 8 バイトを使用します。

プログラムの実行が完了した後、これらのバイトは解放されません。

#include <iostream>
using namespace std;

void data_leak() { double *pointer = new double(28.54); }
int main() { data_leak(); }

上記のコード ブロックに次のループ コードを追加すると、100 万バイトが割り当てられますが、解放されることはありません。 このコードを実行する前に、開いているファイルを保存する必要があります。

最新のオペレーティング システムでも問題はありませんが、for ループに命令を追加すると、問題が発生する可能性があります。 これが、メモリ リークが非常に危険な理由です。

for (int j = 0; j < 150000; j++) {
  data_leak();
}

C++ でメモリ リークを処理する

まず、func_to_handle_memory_leak(), という名前の関数を作成し、次にメモリ リークを処理する整数型ポインターを宣言し、キーワード new int() で整数値を割り当てます。

void func_to_handle_memory_leak() { int* ptr = new int(6); }

delete() 関数を使用して以前のメモリをクリアし、プログラム内のメモリ リークを回避します。 関数が呼び出されるとすぐにすべてのアクティビティが実行され、割り当てられたメモリは関数が戻る前に解放されます。

delete (ptr);

main 関数では、func_to_handle_memory_leak() 関数を呼び出します。

int main() {
  func_to_handle_memory_leak();
  return 0;
}

完全なソース コード:

#include <iostream>
using namespace std;

void func_to_handle_memory_leak() {
  int* ptr = new int(6);
  delete (ptr);
}
int main() {
  func_to_handle_memory_leak();
  return 0;
}

C++ でメモリ リークを回避する方法

  1. メモリを手動で管理する代わりに、可能な限りスマート ポインターを利用するように努力する必要があります。
  2. char\* の代わりに、std::string を使用する必要があります。 std::string クラスは超高速で高度に最適化されています。 内部ですべてのメモリ管理を処理します。
  3. C++ でメモリ リークを回避する最善の方法は、プログラム レベルでいくつかの new および delete 呼び出しを行うことです。 動的メモリを必要とするものはすべて、範囲外になったときにメモリを解放する RAII オブジェクト内に埋め込む必要があります。 RAII は、コンストラクターでメモリを割り当て、デストラクタでそれを解放するため、変数が現在のスコープを離れたときにメモリの割り当てが解除されることが保証されます。
  4. new キーワードを使用してメモリを割り当て、delete キーワードを使用してメモリの割り当てを解除し、これら 2つの命令の間にすべてのコードを記述します。

Valgrind を使用して C++ でメモリ リークを検出する

メモリ リークは、システムのメモリが使い果たされて malloc の呼び出しが失敗するまで、顕著な問題として現れないため、最も潜行性の高い種類のプログラミング エラーの 1つです。

実際、ガベージ コレクションを使用しないと、C や C++ などの言語を処理するときにメモリを適切に解放するために、労力の約半分が費やされる可能性があります。

サポートされているツールの現在のリストを表示するには、Valgrind を実行し、コードの実行時に使用するツールを選択します。

memcheck ツールを使用して Valgrind を実行すると、正確なメモリ消費量を確認できます。 すべての free および malloc 呼び出しの要約を提供します。

説明するために、memoryleakdemo という基本的なプログラムを使用します。

#include <stdlib.h>

int main() {
  char *x = new char[100];
  return 0;
}

これにより、対応する free 呼び出しのない malloc 呼び出しのリストを含む、プログラムに関するいくつかの情報が表示されます。

% valgrind --tool=memcheck --leak-check=yes memoryleakdemo

main での malloc の呼び出しがメモリ リークの原因であることはわかっていますが、特定の行番号を特定することはできません。 問題は、デバッグ シンボルを提供する GCC の -g オプションを使用してプログラムをビルドしなかったことです。

次の出力は、デバッグ シンボルを使用してプログラムを再コンパイルした場合に得られるものです。

==2022== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
==2022==    at 0x1B900DD0: malloc (vg_replace_malloc.c:131)
==2022==    by 0x804840F: main (memoryleakdemo.c:5)

CRT ライブラリを使用して C++ のメモリ リークを見つける

メモリ リークは、Visual Studio デバッガーと C ランタイム (CRT) ライブラリ を使用して特定および特定できます。

メモリ リーク検出を有効にする

デバッグ ヒープ関数をアクティブにするには、アプリケーションに次のステートメントを含める必要があります。

#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#include <stdlib.h>

これらのコマンドを使用してデバッグ ヒープ関数をアクティブにした後、アプリケーションの終了ポイントの前に _CrtDumpMemoryLeaks への呼び出しを挿入して、プログラムの終了時にメモリ リーク レポートが表示されるようにすることができます。

_CrtDumpMemoryLeaks();

_CrtDumpMemoryLeaks が実行されると、メモリー リーク レポートは Output ウィンドウの Debug タブに送信されます。 これがデフォルトの動作です。

_CrtSetReportMode 関数を使用して、レポートを別の場所に送信できます。

CRT ライブラリ メモリ リークの出力:

アプリケーションが _CRTDBG MAP ALLOC を宣言していない場合、_CrtDumpMemoryLeaks は以下に示すようなメモリ リーク レポートを生成します。

Detected memory leaks!
Dumping objects ->
{18} normal block at 0x00780E80, 64 bytes long.
 Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

アプリケーションで _CRTDBG MAP ALLOC が指定されている場合、メモリ リーク レポートは次のようになります。

Detected memory leaks!
Dumping objects ->
c:\users\username\documents\projects\leaktest\leaktest.cpp(20) : {18}
 normal block at 0x00780E80, 64 bytes long.
 Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.