C++ 動的メモリ

動的メモリが C++ でどのように動作するかを理解することは、一人前の C++ プログラマーになるために不可欠です。C++ プログラムのメモリは2つの部分に分けられます:

  • スタック:関数内部で宣言されたすべての変数はスタックメモリを占有します。
  • ヒープ:これはプログラム内で未使用のメモリであり、プログラムの実行時に動的メモリ割り当てに使用できます。

多くの場合、特定の変数に特定の情報を格納するためにどれだけのメモリが必要かを事前に予測することはできません。必要なメモリのサイズは実行時に決定する必要があります。

C++では、特別な演算子を使用して、実行時に指定された型の変数にヒープ内のメモリを割り当てることができます。これにより、割り当てられた空間のアドレスが返されます。この演算子こそがnew演算子です。

動的に割り当てたメモリ空間が不要になった場合は、delete演算子を使用して、以前に new 演算子によって割り当てられたメモリを解放できます。

new と delete 演算子

次に、new 演算子を使用して任意のデータ型にメモリを動的に割り当てる一般的な構文を示します:

new data-type;

ここで、data-type配列を含む任意の組み込みデータ型、またはクラスや構造体を含むユーザー定義の任意のデータ型を指定できます。まず組み込みデータ型を見てみましょう。例えば、double 型へのポインタを定義し、メモリを要求できます。このメモリは実行時に割り当てられます。次のステートメントのようにnew演算子を使用してこれを実行できます:

double* pvalue = NULL; // null に初期化されたポインタ pvalue = new double; // 変数にメモリを要求

フリーストア領域を使い果たした場合、メモリの割り当てに成功しない可能性があります。そのため、new 演算子が NULL ポインタを返すかどうかを確認し、適切な対処を行うことをお勧めします:

double* pvalue = NULL; if( !(pvalue = new double )) { cout << "Error: out of memory." <<endl; exit(1); }

malloc()malloc() 関数は C 言語の時代から登場しており、C++ にも依然として存在します。ただし、malloc() 関数はできるだけ使用しないことをお勧めします。new は malloc() 関数と比較して、メモリを割り当てるだけでなくオブジェクトも生成するという主な利点があります。

いつでも、動的にメモリを割り当てた変数が不要になったと判断した場合は、delete 演算子を使用して、その変数が占有しているメモリを解放できます。次に示すとおりです:

delete pvalue;        // 释放 pvalue 所指向的内存

次の例では、上記の概念を使用して、new と delete 演算子の使い方を示しています:

例

#include <iostream> using namespace std; int main () { double* pvalue = NULL; // null に初期化されたポインタ pvalue = new double; // 変数にメモリを要求 *pvalue = 29494.99; // 割り当てられたアドレスに値を格納 cout << "Value of pvalue : " << *pvalue << endl; delete pvalue; // メモリを解放 return 0; }

上記のコードをコンパイルして実行すると、次の結果が生成されます:

Value of pvalue : 29495

配列の動的メモリ割り当て

文字配列(20文字の文字列)にメモリを割り当てたい場合、上記の例の構文を使用して配列にメモリを動的に割り当てることができます。次に示すとおりです:

char* pvalue  = NULL;   // 初始化为 null 的指针
pvalue  = new char[20]; // 为变量请求内存

先ほど作成した配列を削除するには、ステートメントは次のようになります:

delete [] pvalue;        // 删除 pvalue 所指向的数组

以下は、多次元配列にメモリを割り当てるための new 演算子の一般的な構文です:

一次元配列

// 動的割り当て、配列の長さは m int *array=new int [m]; // メモリ解放 delete [] array;

二次元配列

int **array; // 配列の1次元目の長さを m、2次元目の長さを n と仮定 // スペースを動的に割り当て array = new int *[m]; for( int i=0; i<m; i++ ) { array[i] = new int [n]; } // 解放 for( int i=0; i<m; i++ ) { delete [] array[i]; } delete [] array;

二次元配列のテスト例:

例

#include <iostream> using namespace std; int main() { int **p; int i,j; //p[4][8] // 4行8列の2次元データの割り当てを開始 p = new int *[4]; for(i=0;i<4;i++){ p[i]=new int [8]; } for(i=0; i<4; i++){ for(j=0; j<8; j++){ p[i][j] = j*i; } } // データを出力 for(i=0; i<4; i++){ for(j=0; j<8; j++) { if(j==0) cout<<endl; cout<<p[i][j]<<"\t"; } } // 確保したヒープの解放を開始 for(i=0; i<4; i++){ delete [] p[i]; } delete [] p; return 0; }

三次元配列

int ***array; // 配列の1次元目を m、2次元目を n、3次元目を h と仮定 // スペースを動的に割り当て array = new int **[m]; for( int i=0; i<m; i++ ) { array[i] = new int *[n]; for( int j=0; j<n; j++ ) { array[i][j] = new int [h]; } } // 解放 for( int i=0; i<m; i++ ) { for( int j=0; j<n; j++ ) { delete[] array[i][j]; } delete[] array[i]; } delete[] array;

三次元配列のテスト例:

例

#include <iostream> using namespace std; int main() { int i,j,k; // p[2][3][4] int ***p; p = new int **[2]; for(i=0; i<2; i++) { p[i]=new int *[3]; for(j=0; j<3; j++) p[i][j]=new int[4]; } // p[i][j][k] の3次元データを出力 for(i=0; i<2; i++) { for(j=0; j<3; j++) { for(k=0;k<4;k++) { p[i][j][k]=i+j+k; cout<<p[i][j][k]<<" "; } cout<<endl; } cout<<endl; } // メモリを解放 for(i=0; i<2; i++) { for(j=0; j<3; j++) { delete [] p[i][j]; } } for(i=0; i<2; i++) { delete [] p[i]; } delete [] p; return 0; }

オブジェクトの動的メモリ割り当て

オブジェクトは単純なデータ型と何ら変わりません。例えば、次のコードを見てください。オブジェクトの配列を使用してこの概念を明確にします:

例

#include <iostream> using namespace std; class Box { public: Box() { cout << "コンストラクタが呼び出されました!" <<endl; } ~Box() { cout << "デストラクタが呼び出されました!" <<endl; } }; int main( ) { Box* myBoxArray = new Box[4]; delete [] myBoxArray; // 配列を削除 return 0; }

4つの Box オブジェクトを含む配列にメモリを割り当てる場合、コンストラクタは4回呼び出されます。同様に、これらのオブジェクトを削除する際、デストラクタも同じ回数(4回)呼び出されます。

上記のコードをコンパイルして実行すると、次の結果が生成されます:

コンストラクタを呼び出します!
コンストラクタを呼び出します!
コンストラクタを呼び出します!
コンストラクタを呼び出します!
デストラクタを呼び出します!
デストラクタを呼び出します!
デストラクタを呼び出します!
デストラクタを呼び出します!
その他の拡張