やっかいなメモリ処理

プログラミングをしているとメモリ処理に手こずります。 あらかじめ使用するメモリの量が決まっていればいいのですが、プログラムによっては使用するユーザーによって使用するメモリが決定する場合が多くあります。 例えば、int型のデータを100こ使用する場合は

int data[100];

でいいのですが、使用者によって100だったり200だったりする場合は

int data[??];

と設定すればいいかがわかりません。ここでは、メモリ処理について解説していきます。

メモリ処理の方法

ユーザーによって使用するメモリが異なる場合は、

  • メモリを多めに確保する
  • プログラム中にメモリを確保する(動的メモリ処理)

前者の場合は、単に多めにメモリを確保すればOKです。

メモリの管理が簡単ですが、メモリの無駄遣いが生じます。

後者の場合は、mallocやcalloc命令を使用して必要なメモリを確保します。

メモリの無駄遣いを防止できますが、メモリの管理が大変だったりメモリの確保に失敗する場合があります。

前者については

int data[100000];

と単純なので解説は必要ないと思います。一方、後者は複雑なので解説します。

動的なメモリ処理

動的なメモリ処理はユーザーによってメモリの量を調整できますが、処理が複雑になったりメモリ確保に失敗するリスクがあります。 プログラマはメモリ確保の失敗を考慮してプログラムを組む必要があります。 ここでは、C言語における基礎的なメモリ処理の命令の解説から入り、注意事項について紹介します。

C言語での基本的なメモリ処理の命令

メモリ処理の命令を使用するには

#include <stdlib.h>

でインクルードする必要があります。忘れないように注意しましょう。 はじめにメモリ確保の命令を解説します。メモリ確保では次の命令を使用します。

メモリを確保する命令 calloc

void *calloc(size_t n, size_t size);

大きさがsizeであるオブジェクトn個分の配列領域を確保します。その領域はすべてのビットを0で初期化します。例えば、

int *x;
x=calloc(10, sizeof(int));

でx[0],…,x[9]の配列のメモリが確保されます。すべてのビットは0で初期化されているのでx[0],…,x[9]は0となります。 ※sizeof()で括弧内の大きさを返します。

メモリを確保する命令 malloc

void *malloc(size_t size);

大きさがsizeであるオブジェクト領域を確保します。確保されたオブジェクトの値は初期化されません。ですから、必要なら自分で初期値を初期化しましょう。 使い方の例としては

int *x;
x=malloc(sizeof(int)*10);

でx[0],…,x[9]の配列のメモリが確保されます。ビットは初期化されていません。 callocもmallocもどちらでもいいのですが、mallocの方は初期化しないので自由度が高いといえます。 好きな方で確保してください。

メモリの解放 free

メモリを確保したら解放しなければなりません。 メモリを開放しないとどんどん使用出来るメモリが減っていきます。 これが動的メモリ処理の面倒な点です。

メモリを使い終わったら開放しましょう。

メモリの解法には以下の命令を使用します。

void free(void *x);

xに開放するメモリのアドレスをかきます。

動的メモリ処理のポイント

動的メモリ処理を行う際の注意事項を述べます。

メモリ確保の失敗に備える

まず第一に

メモリ確保は失敗する場合がある

ということです。例えば、

int *x;
x=malloc(sizeof(int)*10);

でメモリ確保に失敗する場合があります。そこで

int *x;
x=malloc(sizeof(int)*10);
if(x==NULL){
      メモリ確保に失敗した場合の処理;
}

とします。こうすることで、メモリ確保に失敗してもプログラムは止まりません。 処理の例としては、メモリに蓄えられているデータを一時的にファイルとして保存しておけばいいでしょう。 例えば、ベクトルならCSVファイルとしてベクトルの内容を記憶しておけばいいでしょう。 ただ、処理の方法は人それぞれなので自分で処理の方法を考えてコーディングしましょう。

使用しないアドレスにはNULLを

第二に

使用していない変数のアドレスはNULLにしておく

ということです。例えば、

int *x=NULL;
x=malloc(sizeof(10));
if(x==NULL){
      メモリ確保に失敗したときの処理;
}
free(x);
x=NULL;

とコーディングしましょう。

メモリ確保前にポインタがNULLかチェック

第三に

メモリ確保の前にポインタが使用済みか確認しましょう。

ポインタが使用しているのでメモリ確保しようとするとエラーが生じます。よって、

int *x=NULL;
if(x!=NULL){
    free(x);
    x=NULL;
}
x=malloc(sizeof(int)*10);

とします。これで解法し忘れていてもメモリを確保できます(メモリリークの恐れがありますが)。

なぜメモリ確保に失敗するか

mallocやcallocはメモリ確保に失敗する場合があるとかきました。 ではなぜメモリ確保に失敗するのでしょうか?

メモリがない

単純にメモリがないとメモリを確保できません。 メモリがない場合にmallocやcallocは失敗します。

フラグメンテーション

メモリがあってもメモリを確保できない場合があります。 例えば、以下の図のようにメモリが使用されているとします。

フラグメンテーションの説明図

一応、400バイトの空きはありますが、まとまっていないので400バイトのメモリは確保できません。 これがフラグメンテーションと呼ばれます。 100バイト以下のメモリなら確保できます。

フラグメンテーションを防ぐには

フラグメンテーションを防ぐにはあらかじめ多めにデータを確保しておくことです。 まとめてデータ領域を確保することで細切れしませんよね。 もしくは、メモリ確保の量を小さくすることです。 メモリ確保の量が小さいと、隙間に入りやすくなるのでフラグメンテーションが生じる確率が下がります。

結局メモリ処理はどうしたらいいのか

結局メモリ処理は

  • 事前に多めにメモリを確保する
  • 動的にメモリ処理をする

のどちらがいいのでしょうか?ということになります。 基本的にどちらを使用するかはプログラムによるのでプログラマの腕の見せ所です。 両者のメリットとデメリットをいかにまとめるのでプログラムを組む際に参考にしてみてください。

事前にメモリを多めに確保する

【メリット】

  • 処理が簡単
  • プログラム中にエラーが起きにくい

【デメリット】

  • メモリの無駄遣いが生じる
  • プログラムを実行する環境によってはメモリが確保できない場合がある

動的にメモリ処理する

【メリット】

  • 使用するメモリを状況によって管理できる

【デメリット】

  • メモリ管理が面倒
  • メモリを確保できない場合がある

著者:安井 真人(やすい まさと)