第71章 借りたら返す

 今までは、変数を作ることによってメモリを使用してきました。しかし、それだけでは不便なことがあります。その「不便なこと」については後の章で話すとして、今回はもう1つのメモリの確保法について話そうと思います。


 では、今回の要点です。


 では、いってみましょう。


 今までは変数を作ってメモリを利用してきましたが、実はメモリはもっと自由に利用することができます。

 とりあえず、次のプログラムを見て下さい。

プログラム実行結果
// New1.cpp
#include <iostream.h>

int main()
{
    int* p;

    p = new int;

    *p = 0;
    cout << *p << endl;

    delete p;

    return 0;
}
0

 何やら、ポインタを作ってますね。次に、それに new int という謎のものを代入しています。

 で、次に、何とこのポインタを使って参照を行っているではありませんか! ポインタを変数のアドレスで初期化していないというのにですよ! で、何事もなかったかのように *p を表示しています。

 で、最後に何やら delete という謎のまじないをして、プログラムが終わっています。謎だらけですね。

 ポインタは参照先がきちんとしていないと使ってはダメなはずです。ということは、この new int というのは参照可能な何らかのアドレスを返すに違いありません。一体これは何者なのでしょうか?


 実は、この new というのはメモリを確保する演算子だったのです。しかし、「メモリを確保する」とはどういうことなのでしょうか?

 メモリというのは、いろいろなプログラムが使用しています。しかし、メモリを好き勝手に使っていると、他のプログラムが、時には自分が使っていたメモリすらも横取りしてしまい、その値を壊してしまうことになるかもしれません。

 そこで、メモリを使いたいときには「これだけメモリを使いたいんですけど...」とOSに頼む必要があるのです。それが「メモリを確保する」ということなのです。

 関数や静的変数は、プログラムをメモリ上にロードするときにOSが確認します。試しにものすごい大きな静的変数を持つプログラムを作ってみて下さい。それをいくつも起動すると、いずれメモリが足りなくなって起動できなくなると思います。OSが「もうメモリが足りないよ」と言ってくるのです。

 自動変数は、あらかじめ自動変数用の領域が最初に用意され、そこに順次値を置いていくようになっています。つまりは、これもプログラムをメモリ上にロードするときにOSが確認するわけです。

 一方この new というのは、プログラムを実行している最中に新たにメモリを確保したいときに使うのです。OSが「使っていいよ」と言ってくれれば、そのメモリを使うことができるようになるわけです。このように、プログラムの実行中にメモリを確保することを「メモリを動的に確保する」といいます。

 そのメモリの位置、つまりアドレスは new が返してくれます。これをポインタに入れておけば、新たに確保したメモリを使えるようになるわけですね。第33章で言ったように「アドレスに * をつけると、そこのメモリを扱える」ので、*p とすれば新たに確保したメモリを操作することができるのです。

 この、new で確保できるメモリのある領域のことをフリーストア(またはヒープ)と呼びます。

 で、このメモリはあくまで「借り物」です。ジャイアンじゃないのですから、借りたものはきちんと返すのが筋です。借りっぱなしだと、他のプログラムがいつまでたってもそのメモリが使えなくなってしまいますからね。そのメモリが必要な間は返す必要はありませんが、必要がなくなったらきちんとOSにメモリを返す必要があります。このことを「メモリを解放する」と呼びます。

 そのメモリを解放するための演算子が delete なわけです。delete には確保したメモリのアドレスを渡します。これできちんとメモリが解放されて、プログラムをようやく正しく終わらせることができるわけです。

 new で確保したメモリは、最終的には全て delete で解放して下さい。これは最上級に重要なことです。

 例えば、new で確保して、そのアドレスを p というポインタに入れておくとします。で、この p を別の変数に保存したり、delete したりしないまま p の値を変えてしまうと、もうこの領域は自分で開放することは二度とできなくなってしまいます。他にも、自動変数でアドレスを受けた後にそのまま関数を抜けてしまうと、やはりもう二度と解放できなくなってしまいます。こうした処理が何度も行われると、知らない間にメモリを食いつぶし、最後にはメモリが一杯になってしまうことさえあります。

 このように、メモリを解放していないがために、その領域を再利用できなくなってしまうことをメモリリークと言います。メモリリークは原因箇所を特定することが難しく、メモリリークはプログラムのバグにおいて3本の指に入る最悪なバグです。借りたものはきちんと返さないといけないのです。このことは肝に銘じておいて下さい。


 で、もうちょっと話すことがありますね。一体、さっきはどれだけのメモリを確保したのでしょうか? そして、それはどのように指定することができるのでしょうか?

 上のプログラムでは new int と書きました。こうすると、int 型の変数が入るだけのメモリが確保されます。new char とすると char 型の変数が入るだけのメモリが、new double とすると double 型の変数が入るだけのメモリが確保されます。

 つまりは、new <型> とすればその型の変数が入るだけのメモリが確保されるのです。

 この型には配列を指定することも、ポインタを指定することもできます。(配列を指定するときの話は次回に回します。)ただ、参照だけは作れません。参照自体は操作できないということは、参照への参照、参照へのポインタは作れないということです。なので、メモリを確保しても使うことはできません。まぁ、初期化もできませんしね。

 で、new int とすればそのアドレスは int* で、new char とすればそのアドレスは char* で受けます。このように、new <型> としたときは、そのアドレスは普通は <型> へのポインタで受けます

 で、delete でメモリを解放するときには、この new から返ってきたアドレスを渡せばいいです。アドレスさえ正しければ、new のアドレスを受けたときのポインタを使わなくても構いません。


 さて、メモリはOSに頼んで貸してもらうと言いました。このときメモリが一杯だとどうなるのでしょうか? そのときは、OSは「ちょっと貸せませんねぇ」と言ってきます。つまり、メモリの確保に失敗します

 そういうときは、new は「例のアレ」を返してきます。そうです。第63章でやったヌルポインタです。ここでも、ヌルポインタは失敗したときの目印として使われているわけですね。

 なので、上のプログラムは正しくは次のように書き直す必要があるのです。

// New1b.cpp
#include <iostream.h>

int main()
{
    int* p;

    p = new int;
    if(p == NULL)
        return 0;

    *p = 0;
    cout << *p << endl;

    delete p;

    return 0;
}

 この赤で書かれた部分がないと、メモリの確保に失敗したときエラーになってしまいますね。ファイルの時もそうでしたが、「メモリの確保は失敗することがある」ということを必ず考えてプログラムを組む必要があります。


補足

 C++ の新しい仕様では、new が失敗した場合の振る舞いが違います。具体的に言うと bad_alloc 例外が投げられるわけですが、今の段階では例外を教えてないのでよく分からないと思います。新しいコンパイラを使ってる人は、多少大雑把に言うと、今のところは new 失敗時の処理を無視して構いません(失敗した時点でプログラムが終了します)。

 例外に関しては第2部第45章を、bad_alloc に関しては第3部第49章を参照してください。


 それでは、今回の要点です。


 メモリの動的確保はプログラムにおいてとても重要です。その一方で、メモリリークの危険性を常にはらんでいます。new と delete の対応には充分気を回して下さい。

 次回は配列の確保について話したいと思います。では、次回まで。


第70章 仰山のファイル | 第72章 借りたら返す2

Last update was done on 2004.1.22

この講座の著作権はロベールが保有しています