このページをはてなブックマークに追加このページを含むはてなブックマーク このページをlivedoor クリップに追加このページを含むlivedoor クリップ

目次

malloc

  • mallo関数を使用するにはstdlib.hをインクルードする必要がある。
  • OSからメモリを動的に借りるための関数。

mallocのプロトタイプ

void *malloc(size_t size)

mallocの引数

  • 第1引数
    • 動的に確保したいメモリ領域のサイズ(バイト単位)

mallocの戻り値

 size分の大きさのメモリをヒープ上に作成して、そのメモリ領域の先頭アドレスを返す。もし失敗するとNULLを返す。

mallocの特徴

  • 作成されたメモリ領域は初期化されない。
  • malloc内部ではOSからメモリを借りるシステムコールが呼び出されている。
  • 動的に割り当てたから必ずfreeすること。freeする前にプログラムが終了してしまうと、どこからも参照できないデータがメモリに残ってしまい、メモリリークしてしまう。
  • mallocの引数にはメモリ領域を与えることになるが、ここに0を与えるとどうなるかということは処理系に依存する。
    • 処理系によってはNULLが返されたり、アドレスが返ったりする。
    • つまり、malloc(0)の結果をあてにしたプログラムは移植性が悪いプログラムになるので注意が必要である。
  • C++にもmalloc,freeがあるが、動的メモリを扱うためのnew,deleteを使った方がよい。

テクニック

動的にメモリを確保し、そこに文字列を格納して表示し、解放する

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 
 
 
-
|
|
|
|
|
|
|
!
#include <stdio.h>
#include <stdlib.h>
 
int main(void){
    char *p;
 
    p = malloc(10);    /* 10バイトの領域を確保 */
    strcpy_s(p, 10, "abced");
    printf("p = %s\n", p);
    free(p);        /* 必要がなくなったら解放 */
    return 0;
}

 実行結果は次の通りである。

p = abced

例:入力を動的に確保した領域に記憶する

 1024文字という制限はあるが、1024バイトの配列を用意するよりは空間効率性はよい。

fgets(buf, 1024, fp);	/* 1行分を読み込む */
chop(buf);				/* 改行文字を削除する */
p = malloc(sizeof(char) * (strlen(buf) + 1));
strcpy(p, buf);		/* 確保した領域にbufをコピーする */
return p;

ヌル文字を考慮して引数を与える

 文字列を格納するために必要な大きさは、実際の文字列にヌル文字分の1つを加えたものとなる。strlen関数はヌル文字を計算に入れないので、もしstrlen関数を利用する場合で、次のように+1してから引数に与える必要がある。

char *p;
char *str = "ABC";
p = malloc(strlen(str) + 1);
strcpy(p, str);

malloc関数の動的確保における判定を簡略化する

 次のような関数を用意しておけば便利かもしれない。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
-
|
|
|
-
|
|
!
|
!
void *my_malloc(size_t size){
    void *p;
 
    p = malloc(size);
    if (p == NULL) {
        fprintf(stderr, "メモリが足りません。\n");
        exit(1);
    }
    return p;
}

 void*は任意の型へのポインタ変数に代入できるポインタ型である。malloc関数の戻り値の型もvoid*である。

 size_tはstdlib.hファイルでtypedefされている型である。unsigned intやunsigned longであることが多い。

assert利用によるmallocの戻り値のチェック

 assertはマクロであり、引数に与えた式が偽ののときにプログラムを異常終了させる。assertを使うためには、assert.hをインクルードする必要がある。

 次のようにすることで、ポインタであるworkにはmallocの結果が格納される。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 
 
 
 
-
|
|
|
!
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
 
int main(void){
    char *work;
    work = malloc(1024);
    assert(work);
}

 workがNULLのときに、プログラムは異常終了する。

 このassertはプログラムがデバッグ用にコンパイルされているときだけに、有効に働く。マクロNDEBUGが定義されている場合に、assertは空になる。

 assertには式を引数に与えることができるので、次のような使い方が可能である。プログラムが正しく動くときに期待するデータの値をassertでチェックできるのである。

assert(count < 10);
assert(str[i] == '\0');

エラーを減らす定石

 昔はプログラムが暴走してしまうとマシンが暴走してしまうOSがあった。このような時代ではmallocの戻り値を必ずチェックして、メモリ不足になってもプログラムが暴走しないようにすることが重要視されていた。

 しかし、現在はプロセスごとに仮想メモリが割り当てられているので、メモリは十分に大きな容量を持っているといえる。だからといってmallocnの戻り値をまったくチェックしないでよいかというとそうではない。

 エラーを減らすための注意点がいくつか存在するので、紹介する。

・malloc利用時はstdlib.hをインクルードする。

・ローカル変数は初期化しておいたほうが無難。

char *p = NULL;

・必要な分だけアロケーションする。

・malloc関数の戻り値がNULLであることを確かめずに、malloc関数が返したポインタを使って文字列をコピーしてしまうと問題が起こる場合がある。NULLポインタを参照してしまうと、プログラムは異常終了してしまう。

 よって、malloc関数を呼び出した後は、次のようなチェックが必要である。

if (p == NULL) {
	fprintf(stderr, "メモリ不足です。\n")
};

[補講]これはmalloc関数に限った話ではなく、C言語の鉄則として「ポインタを使う場合は、それがNULLでないことを確かめなくてはならない」だろう。 ◇

・アロケーションした領域を越えない入力にする。

strcpy(p, "ABCDE");

・不要になった時点でfreeする。

malloc関数が返すvoid*型ポインタを型キャストする

 mallocで返されるアドレスの型はvoid*である。void*型のポインタとは、まだ型を持たないため、そのままでは間接参照に使えないポインタのことである。void*は他の型へのポインタに代入しても、コンパイラはワーニングしない。そのため、実際のポインタの型に合わせてキャストする必要がないのである。ただし、stdlib.hをインクルードしなければならない。もしインクルードすることを忘れてしまうと、intを返す関数と見なされてしまう*1

 キャストしたことを明示したければ、次のようにすればよい。

int *p;
p = (int*)malloc(sizeof(int));

malloc関数の戻り値が格納される変数とfree関数の引数は対にして使う

char *buf_p;
buf_p = malloc(1024);

 このようにbuf_pというアドレスがあれば、buf_pをfreeさせる。

free(buf_p);

 このように対にしておかないと、巨大なプログラムになったときにどのmallocに対応するfreeかどうかがわからなくなってしまう。

 よって、buf_pを加工する必要があれば、一時的なポインタtmp_buf_pを用意しておき、一時的なポインタの方を加工用に利用する。

例:

int i;
char *buf_p, tmp_buf_p;
buf_p = malloc(1024);
tmp_buf_p = buf_p;

for (i = 0; argv[i] != '\0'; i++) {
	*tmp_buf_p++ = argv[i];
}
*tmp_buf_p = '\0';

printf("%s\n", buf_p);
free(buf_p);

参考文献

  • 『プログラミング言語C 第2版』
  • 『C言語ポインタが理解できない理由』
  • 『改訂新版 Cプログラミング診断室』
  • 『C言語体当たり学習徹底入門』


*1 これはプロトタイプ宣言が行われていない関数すべてに当てはまること