ショートカット
ファシリテーター × あり方
コーディングの向こう側
Hello, ANOTHER world!
オブジェクト指向のはなし
プログラミングのはなし
C言語実力診断クイズ
eSkillBooks
Hello, ANOTHER world!

ヒープについてもっと詳しく

ヒープメモリを malloc で確保するときは、使いたいメモリのサイズを指定して、戻り値として確保できたメモリの先頭アドレスを得ます。メモリには限りがあるので、確保できない場合も当然あります。確保されたメモリにアクセスするには、malloc で得たアドレスを使います。サイズが s で先頭アドレスが a なら、a 〜 a+s-1 の範囲が使って良い領域です。
さて、ヒープからとるメモリのアドレスは、いつも一定ではありません。もちろんあらかじめ予測することもできません。そこで、malloc の戻り値として得たアドレスをどこかに保管しておいて、ヒープメモリへはそこから間接的にアクセスしないといけません。つまり、ポインタを使うわけです。
ここで使うポインタは、グローバル(関数の外側にある)変数でも、ローカル(関数の内側にある)変数でも、何でも構いません。が、多くの場合はローカル変数を使うのが望ましいでしょう。つまり、スタックの中に確保される auto 変数と組み合わせるのです。
図解してみましょう。まず、グローバル変数 gs_p にヒープから取得したメモリの先頭アドレスを入れた場合はこうなります。ヒープには、このポインタ gs_p 経由で間接的にアクセスします。
ちなみに変数名についている "gs_" という部分は私が気に入っている書き方で、"Global Scope" の略です。
次に、関数の中のローカル変数 p を使った場合はこうです。ヒープには、ポインタ p 経由で間接的にアクセスします。
いや、もっと正確な図を使いましょう。ローカル変数はスタック中にあるので、本当はこうですね。
さて、データには構造があるということをここで思い出しましょう。ヒープから取り出したのはただのメモリ領域なので、1バイト分のメモリがたくさん集まってできた塊にしか見えません(こういうのを「メモリブロック」と呼びます)。このメモリを、どうしたら構造を持ったデータにできるでしょうか?
例えば、int や double などの型は、それだけで「整数」「浮動小数」というデータの構造を表現しています。つまり、型を与えれば構造を与えたことになるでしょう。また、このような単純な型を並べた配列もデータ構造の一種ですし、構造体は(名前からしてそうとしか考えられないですが)より複雑なデータ構造を表します(C++ の class もそうです)。
malloc で取り出したメモリブロックに型を与えるには、ポインタのキャストを使います。今使いたい型を T とすると、次のように書くことになります。これはもう、決まり文句というか、慣用句のようなものです。
T *p = (T *)malloc( sizeof(T) );
ここで、ヒープを使った "Hello, world" プログラムを(無理矢理ですが)作ってみましょう。まずデータの構造を決めます。ここでは文字列を格納するために char の配列を封じ込めた構造体を用意します。
typedef struct {
     char m_pcMsg[50];
} MessageArea;
malloc を呼ぶときにはこの構造体のサイズを指定し、確保に成功したら構造体のポインタにメモリブロックの先頭アドレスを保管します。それから "Hello, world" と表示する処理をして、最後に(ここを忘れがちなので注意!)メモリブロックを free で解放します。もし解放しないと、使えるメモリがどんどんなくなってしまいます。ポインタにローカル変数を使っているので、解放のタイミングは簡単でしょう。いらなくなったら即、解放しましょう。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
     char m_pcMsg[50];
} MessageArea;

static void ShowMessage( MessageArea *pMsg );

int main(void)
{
     MessageArea *p = (MessageArea *)malloc( sizeof(MessageArea) );

     if ( p ) {
          strcpy( p->m_pcMsg, "Hello, HEAP world!" );

          ShowMessage( p );

          free( p );
          p = 0;
     }
     else {
          ; /* Failed to allocate! */
     }

     return EXIT_SUCCESS;
}

static void ShowMessage( MessageArea *pMsg )
{
     puts( pMsg->m_pcMsg );
}
実際には、プログラムが終了するときに、そのプロセスで使ったメモリはすべて解放されます。なので、このサンプルのような短いプログラムのときは、実のところ free でメモリを解放してもしなくてもあまり変わりありません。しかし、大きなプログラムや常駐するプログラムではそうはいきません。「クセをつける」という意味で、小さなプログラムでも使ったメモリは解放するという習慣にするのが良いと思います。

("Hello, ANOTHER world!" は2001年11月から2002年11月にかけて作成されたコンテンツです。)