第四回-02 : new/delete によるメモリの動的管理

本ページでは「new/delete によるメモリの動的管理」について解説する。


malloc/free によるメモリの動的管理 (C 言語)

new/delete によるメモリ管理に触れる前に、
第二回-04 : メモリとポインタ」で取り扱った malloc/free による動的メモリ管理の復習をしておこう。

この手法は C 言語で主に用いられるもので、実は C++ で使われることはあまりない。
ここでは new/delete への導入として malloc/free について触れる。

まず、char 型のポインタ *p を定義し、そのポインタの指す先に要素数 SIZE=255 個の char 型の配列を確保することを考えよう。
第二回-04 : メモリとポインタ」で取り扱ったように、これは

 p = (char *)malloc(SIZE);


なる命令で実現出来る。この際、ヒープ領域というメモリ領域から領域が確保されるのであった。
なお、(char *) は「malloc によって返される void 型のポインタを char 型のポインタにキャスト」している (この文の意味がわからなくてもあまり気にしなくて良い)。

注意しなければならないことだが、この時 malloc は要素数 SIZE の配列を確保しているのではなく、 SIZE バイトの領域を確保しているだけなのである。
SIZE バイトの領域が要素数 SIZE の配列として使える理由は、char 型の一要素が実は1バイトであるからである。

では、一要素が1バイトではない型、例えば int 型の要素をヒープ領域から確保するにはどうすればよいだろうか?
int 型1要素のバイト数を調べるには sizeof 演算子を用いるのが良い。(ちなみに、多くの環境では sizeof(int)=4 バイトである)
よって、要素数が SIZE 個である int 型の配列をヒープ領域を確保するには、sizeof(int)*SIZE バイトの領域を確保すれば良い。

ここで、int 型のメモリを動的に割り当てるプログラム例を以下にあげる。 (このプログラムはクラスを用いていない)

#include <iostream>
#include <cstdlib>  // for malloc/free
using namespace std;

#define SIZE 255

int main(){

  int *p1;
  int *p2;

  p1 = (int *)malloc(sizeof(int));        // int 型変数1個のメモリ確保

  p2 = (int *)malloc(sizeof(int)*SIZE);   // int 型の配列 (要素数) のメモリ確保


  *p1 = 1000;

  cout << "p1 が指している整数型は: " << *p1 << "\n";

  // p2 の利用部は各自で例を考えて書いてみること

  free(p1);  // メモリの解放
  free(p2);

  return 0;
}


free によるメモリの解放を忘れない癖をつけよう。 なお、このときのメモリの模式図は次のようになる。



この例では に気づいてもらえれば良い。


new/delete によるメモリの動的管理 (C++): 組み込み型の場合

C 言語では malloc/free を用いてメモリ管理を行ったが、C++ では new/delete を使うのが普通である。
上のプログラムを new/delete を使って書き直すと以下のようになる。

#include <iostream>
using namespace std;

#define SIZE 255

int main(){

  int *p1;
  int *p2;

  p1 = new int;         // () int 型変数1個のメモリ確保

  p2 = new int[SIZE];   // () int 型の配列 (要素数 SIZE) のメモリ確保

  *p1 = 1000;

  cout << "p1 が指している整数型は: " << *p1 << "\n";

  // p2 の利用部は各自で例を考えて書いてみること

  delete p1;    // () メモリの解放
  delete[] p2;  // () メモリの解放 (1要素と配列とで異なる!!)

  return 0;
}


メモリの模式図は malloc/free の場合と同じである。

int 型のバイト数 (sizeof(int)) やポインタのキャスト (int *) の必要がなく、記述がシンプルになっていることに気づくだろうか。
なお、delete 文によるメモリの開放は、1要素と配列とで異なることに注意しなければならない。


new/delete によるメモリの動的管理 (C++): クラスの場合

組み込み型 (char, int, double 等) と同様、自分で作成したクラスについても new/delete でメモリの管理を行える。

#include <iostream>
using namespace std;

class samp{

    int i, j;

  public:

    samp(){    // デフォルトコンストラクタ
       cout << "default constructor\n"; 
    }

    samp(int a, int b){    // 引数つきコンストラクタ
       i = a; 
       j = b; 
       cout << "constructor with arguments\n";
    }

    ~samp(){ cout << "destructor\n";}  // デストラクタ

    void set_ij(int a, int b){ i=a; j=b;}
    int get_product(){ return i*j; }

};

int main(){

  samp *p1;
  samp *p2;
  samp *p3;

  p1 = new samp;      // オブジェクト1個   (引数なし)
  p2 = new samp(6,5); // オブジェクト1個   (引数あり)
  p3 = new samp[10];  // オブジェクト10個の配列 (引数なし)

  p1->set_ij(4,5);

  for(int i=0 ; i<10 ; i++){
    p3[i].set_ij(i,i);
  }

  cout << "p1 の積は: " << p1->get_product() << "\n";
  cout << "p2 の積は: " << p2->get_product() << "\n";
  for(int i=0 ; i<10 ; i++)
    cout << "p3[" << i << "]の積は: " << p3[i].get_product() << "\n";

  delete p1;
  delete p2;
  delete[] p3;

  return 0;
}


オブジェクトに対しても同様に new/delete でメモリ管理が出来ていることがわかる。
さらに、コンストラクタとデストラクタがどのように呼ばれるかを体感してもらうためにコンストラクタ/デストラクタ内に cout 文が入っている。

p1 と p3 に対してはデフォルトコンストラクタが、 p2 に対しては引数なしのコンストラクタが起動されることに注意。
また、ドット演算子「.」とアロー演算子「->」の使い分けにも注意しよう。

最後に、「p3 = new samp[10];」のような配列の割当ての際、p2 のように引数を与えて初期化したいと思うかも知れないが、基本的にはそれはできない。

# (以下は自信のある人向け)
#
# 「p3 = new samp[10];」のような配列の動的割当ての際に引数を指定する方法は、次のようにポインタへのポインタを使うのが常套手段である。
# 下の手法を使えば set_ij 関数は必要なくなる。逆に、上の例のように set_ij を使えば無理に下の手法を使う必要はない。
#


  samp **p; //  ポインタへのポインタ

  p = new samp*[10] // samp 型のポインタの配列 10 個を確保

  p[0] = new samp(0,0);
  p[1] = new samp(1,1);
  …

  // 使用する際は p[i]->get_product() のようにアクセス

  for(int i=0 ; i<10 ; i++){  // 各ポインタの指すオブジェクトを削除した後…
   delete p[i];
  }

  delete[] p;	  // ポインタへのポインタを削除




←第四回-01 : オブジェクトの配列第四回-03 : コピーコンストラクタ・代入演算子のオーバーロード→

第四回トップページへ

クラスから入る C++ へ戻る