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

目次

realloc

  • 動的に確保された領域を拡張する。

プリプロセッサ

void *realloc(void *mem, size_t newsize);

引数

  • 第1引数
    • 拡張する領域を指定するためのポインタ。
      • このポインタはmalloc,calloc,reallocのいずれかが返したアドレスでなければならない。
    • 例外としてNULLが与えられた場合は、reallocはmallocと同じ動作をする。
  • 第2引数
    • 拡張したい大きさ。

戻り値

  • 拡張された結果の領域の先頭アドレス。
    • 第1引数に与えたポインタとはずれる可能性がある(仕組みは後述している)。
  • 拡張に失敗するとNULLが返される。

reallocの特徴

  • calloc,malloc,reallocにより作成されたメモリ領域pを保持しながら、再度sizeバイトの大きさに拡張する。
    • 最初はこれくらいで全部のデータが収まると予想してmallocしたとする。しかし、その後確保したメモリ領域では足りなくなることもある。このときの対処法は2つ考えられる。
      • まず1つ目の方法は、十分なメモリをmallocにより新しく確保して、そこにコピーしてしまう方法である。
      • 次に2つ目の方法は、reallocでメモリ領域を拡張する方法である。
  • 失敗するとNULLが返される。
  • 拡張されてできたメモリ領域は初期化されない。
  • メモリが配列A,B,未使用という順番で並んでいたとする。
    • このとき、Bをreallocで拡張するときは、Bのアドレスは変化せずにサイズだけが拡張される。
    • 一方、Aをreallocで拡張するときは、Bが邪魔なのでそのままAを拡張することはできない。そこで、Bの後ろにAをコピーし、そのAの後ろにサイズを拡張していく。つまり、元のAのアドレスとは異なるアドレスが返されることになる。

テクニック

reallocを用いた動的なメモリ確保例

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 
 
 
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
!
|
|
-
|
-
|
|
!
!
|
|
|
-
|
|
!
|
!
#include <stdio.h>
#include <stdlib.h>
 
int main(void){
        int c;            /* 入力文字 */
        int size = 0;        /* 文字数 */
        char *buf_p = NULL;
        char *old_buf_p = NULL;
 
    while (1) {
                /*-----------------------------------------------
                  標準入力から1文字ずつ拡張されたメモリ領域に格納
                  する
                -----------------------------------------------*/
                buf_p = realloc(buf_p, (size + 1) * sizeof(char));
                if (buf_p == NULL) {
                        fprintf(stderr, "メモリ領域の確保に失敗しました。\n");
                        free(old_buf_p);
                        exit(EXIT_FAILURE);
                }
                old_buf_p = buf_p;
                c = fgetc(stdin);
                if (c != EOF) {
                        buf_p[size++] = c;
                } else {
                        buf_p[size] = '\0';
                        break;
                }
        }
        /*-----------------------------------------------
          文字列を表示し、メモリ領域を開放する
        -----------------------------------------------*/
    if (buf_p) {
                puts(buf_p);
                free(buf_p);
        }
        return 0;
}

 tmp_buf_pはループの1回前のbuf_pが格納されている。もし、tmp_buf_pではなくbuf_pをfreeしてしまうと、単にreallocの戻り値NULLをfreeしてしまうことになり、直前まで動的に確保に成功した領域がfreeできないことになってしまう。

 1回目のreallocの動的確保に失敗したとき(最初はNULLが与えられるので内部的にmallocと同等)、free(NULL);が実行される。free(NULL);は何もしないと定義されているので問題ない。

reallocで拡張する領域の大小について

 1文字ずつreallocをしていては効率が悪くなってしまうし、フラグメンテーションも発生しやすいという問題がある。もしreallocをするときは、1度にある程度まとまった量を拡張した方がよいと言える。

割り当て済み領域を初期化する

 realloc関数を使って領域を拡大しても、既存のすべてのデータが保持されている。簡単に初期化したければ、memset関数を用いればよい。具体的に言えば、古いデータの存在する次の位置から新しい領域の末尾までを次のように初期化すればよい。

pmem = pmemnew + BUFF_SIZE;	/* pmemポインタを未初期化部分の先頭位置に設定する */
memset(pmem, 0, BUFF_SIZE);	/* memset関数を使って初期化する */

 サンプルプログラムは次の通りである。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 
 
 
 
 
 
 
-
|
|
|
|
|
|
|
|
|
-
|
|
|
!
-
|
!
|
|
-
|
!
|
|
|
|
|
|
-
|
|
|
|
|
|
|
|
|
|
-
|
!
|
|
|
|
|
|
|
|
|
-
|
|
|
|
!
|
!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define BUFF_SIZE    256        /* バッファのサイズ */
#define NEW_BUFF_SIZE    (BUFF_SIZE * 2)    /* 拡張されたバッファのサイズ */
 
int main(void){
    int i;            /* for文用のカウンタ */
    unsigned char *buf_p = NULL;
    unsigned char *tmp_buf_p = NULL;
    unsigned char *new_buf_p = NULL;
 
    /*-------------------------------------------------------
	  malloc関数を用いてメモリを割り当て、数値を格納し、表示する
	-------------------------------------------------------*/
    tmp_buf_p = (buf_p = (unsigned char *)malloc(BUFF_SIZE));
    if (buf_p == NULL) {
        printf("メモリ領域の確保に失敗しました。\n");
        free(buf_p);
        exit(EXIT_FAILURE);
    }
    for (i = 0; i < BUFF_SIZE; i++) {
        *buf_p++ = (char)i;
    }
    buf_p = tmp_buf_p;    /* buf_pポインタのリセット */
    printf("メモリの拡大前の状態\n");
    for (i = 0; i < BUFF_SIZE; i++) {
        printf("%u ", *buf_p++);
    }
    printf("\n\n");
 
    /*-------------------------------------------------------
	  realloc関数を用いてメモリの領域を拡大する
	-------------------------------------------------------*/
    new_buf_p = realloc(tmp_buf_p, NEW_BUFF_SIZE);
    if (new_buf_p != NULL) {
        /*-----------------------------------------------
		  初期化
		-----------------------------------------------*/
        buf_p = new_buf_p + BUFF_SIZE;
        memset(buf_p, 0, BUFF_SIZE);
        buf_p = new_buf_p;    /* 新しい先頭位置のセット */
        /*-----------------------------------------------
		  拡大後のメモリ内容を表示する
		-----------------------------------------------*/
        printf("メモリの拡大後の状態\n");
        for (i = 0; i < NEW_BUFF_SIZE; i++) {
            printf("%u ", *buf_p++);
        }
        printf("\n");
        /*-----------------------------------------------
		  メモリを解放し、NULLを代入する
		-----------------------------------------------*/
        free(new_buf_p);
        new_buf_p = NULL;
    /*-------------------------------------------------------
	  メモリの拡大に失敗したとときの処理
	-------------------------------------------------------*/
    } else {
        free(new_buf_p);
        new_buf_p = NULL;
        fprintf(stderr, "メモリの拡大に失敗しました。\n");
        exit(EXIT_FAILURE);
    }
    return 0;
}

参考文献

  • 『C言語ポインタが理解できない理由』
  • 『美しいCプログラミング見本帖 〜ポインタ手習い指南〜』