メモリ操作

【C言語】reallocの使い方:動的メモリサイズの再割り当て方法

この記事は、C言語のメモリ管理関数であるreallocを使って、既存の動的メモリブロックのサイズを変更する方法を解説します。

実際のコード例を通して、メモリの再割り当て時に発生しうる注意点や、効果的な利用方法を分かりやすく紹介します。

realloc関数の基本情報

reallocの役割と概要

realloc関数は、既に割り当てられた動的メモリ領域のサイズを変更するための関数です。

この関数を利用すると、以前に確保したメモリブロックの領域を拡大または縮小することができ、プログラム実行中に必要なメモリサイズを柔軟に調整できます。

元々の領域が十分なサイズでなかった場合、reallocは新たにメモリブロックを確保し、元のデータを新しい領域にコピーした後、古い領域を解放します。

利用シーンの概略

  • 動的に入力サイズが変動する場合(例えば、ユーザーからの入力やファイル読み込み時)。
  • メモリ不足を防ぐため、初期に小さめのメモリで開始し、必要に応じて拡大する場合。
  • 最終的な必要メモリサイズが実行時に判明するシナリオで、プログラムの柔軟性を高めるとき。

基本構文とシグニチャ

realloc関数の基本的な構文は以下のようになります。

#include <stdlib.h>
// void* realloc(void* ptr, size_t size);

この関数は、元のメモリブロックのポインタptrと、再割り当て後に必要なサイズsizeを引数としてとります。

パラメータの説明

  • ptr

再割り当て対象となるメモリブロックのポインタです。

当該ポインタがNULLの場合、関数はmallocと同様に新たなメモリブロックを確保します。

  • size

再割り当て後のメモリブロックのサイズ(バイト単位)を指定します。

このサイズには、拡大および縮小の両方のケースを示します。

戻り値とエラー処理

realloc関数は、変更後のメモリブロックへのポインタを返しますが、以下の場合に特別な扱いが必要です。

  • 成功時:新しいメモリブロックの先頭アドレスが返されます。
  • エラー時:メモリ割り当てに失敗するとNULLが返され、元のメモリブロックはそのまま維持されます。

エラーが発生した場合の対処として、返り値がNULLかどうかを必ず確認する必要があり、確認しないとメモリリークや予期しない動作につながる可能性があります。

実際のコード例による解説

動的メモリ初期割り当てとの比較

動的メモリの初期割り当てには、通常はmalloccallocが用いられます。

これらの関数は、指定されたサイズのメモリブロックを割り当てるため、サイズの変更には対応していません。

一方、reallocは既存のメモリブロックのサイズを変更するために使用でき、場合によっては既存のデータをそのまま残しながら新たなサイズに対応できる点が特徴です。

reallocを使ったサイズ変更の実例

以下は、reallocを利用してメモリのサイズ変更を行うシンプルなサンプルコードです。

サンプルコードでは、初めにmallocでメモリを確保し、次にreallocでサイズを拡大または縮小する例を示します。

#include <stdio.h>
#include <stdlib.h>
int main(void) {
    // 初期サイズとして10個の整数分のメモリを確保
    int *array = (int *)malloc(10 * sizeof(int));
    if (array == NULL) {
        printf("Initial allocation failed.\n");
        return 1;
    }
    // 配列に仮の値を代入
    for (int i = 0; i < 10; i++) {
        array[i] = i;
    }
    // メモリ拡大例:20個の整数分のメモリに再割り当て
    int *temp = (int *)realloc(array, 20 * sizeof(int));
    if (temp == NULL) {
        printf("Memory expansion failed.\n");
        free(array);
        return 1;
    }
    array = temp;
    // 拡大後の追加領域に値を設定
    for (int i = 10; i < 20; i++) {
        array[i] = i;
    }
    // 結果を出力
    for (int i = 0; i < 20; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");
    // メモリ縮小例:再び10個の整数分のメモリに再割り当て
    temp = (int *)realloc(array, 10 * sizeof(int));
    if (temp == NULL) {
        printf("Memory reduction failed.\n");
        free(array);
        return 1;
    }
    array = temp;
    // 結果を出力(縮小により後半のデータは失われる)
    for (int i = 0; i < 10; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");
    free(array);
    return 0;
}
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
0 1 2 3 4 5 6 7 8 9

メモリ拡大の場合の注意点

  • 拡大する際、元のデータはそのまま保持されますが、新しく確保された領域は未初期化の状態です。
  • 新規領域に対しては必要に応じて初期化処理を行う必要があります。
  • 使用するサイズが変数や定数で定義されている場合、サイズの計算ミスが発生しないよう注意してください。

メモリ縮小の場合の挙動

  • 縮小操作では、元のメモリブロックの内容が先頭から指定したバイト数分だけコピーされ、後半のデータは失われます。
  • 縮小前に該当データのバックアップが必要な場合、必要なデータのコピー処理を行うことが重要です。

エラー処理の実装例

メモリ割り当て関数全般に言えることですが、reallocを使用する場合もエラー処理は必須です。

以下のサンプルコードは、realloc使用時のエラー処理をどのように実装するかの例です。

#include <stdio.h>
#include <stdlib.h>
int main(void) {
    // 既存のメモリ領域を確保
    int *data = (int *)malloc(5 * sizeof(int));
    if (data == NULL) {
        printf("Initial allocation failed.\n");
        return 1;
    }
    // 何らかのデータを設定
    for (int i = 0; i < 5; i++) {
        data[i] = i * 2;
    }
    // reallocでメモリ割り当てを拡大
    int *newData = (int *)realloc(data, 10 * sizeof(int));
    if (newData == NULL) {
        // エラー発生時はnewDataはNULLとなり、dataはそのまま有効
        printf("Reallocation failed. Original data is still intact.\n");
        free(data);
        return 1;
    }
    data = newData;
    // 初期化されていない領域の初期化処理が必要な場合
    for (int i = 5; i < 10; i++) {
        data[i] = i * 2;
    }
    // 結果を出力
    for (int i = 0; i < 10; i++) {
        printf("%d ", data[i]);
    }
    printf("\n");
    free(data);
    return 0;
}
0 2 4 6 8 10 12 14 16 18

NULLチェックの留意点

  • reallocの戻り値をチェックせずに直接代入してしまうと、失敗した場合に元のポインタを失ってしまい、メモリリークの原因となります。
  • 一旦一時変数に戻り値を格納し、チェックが完了してから元のポインタに再代入する手法が推奨されます。

メモリリーク防止の対策

  • エラー時には必ず元のメモリブロックを解放するコードを記述してください。
  • 不要になったメモリブロックは速やかにfree関数を使い解放することで、メモリリークのリスクを軽減できます。

開発環境での注意事項とポイント

既存データの保持についての注意

realloc関数は、メモリの拡大時には元のデータを保持してくれますが、必ずしも同じメモリブロック内に収まるとは限りません。

そのため、元のポインタが無効になる可能性があるため、再割り当て後は必ず新たなポインタを利用して処理するようにしてください。

また、既存データの構造体や配列の内容が複雑な場合は、再割り当て前にデータの整合性を確認し、必要に応じてバックアップを取ることも検討してください。

再割り当て失敗時の対処方法

reallocが失敗するとNULLが返るため、必ずその時点でエラー対処を行う必要があります。

再割り当てに失敗した場合は、以下の対処方法が考えられます。

  • エラーメッセージを表示し、現在の処理を中断する
  • 既存のメモリブロックを維持し、必要に応じたエラーハンドリング(リトライや代替処理)を行う

再割り当ての操作を行う際は、必ずエラー処理のロジックを実装し、予期せぬ動作を防ぐよう努めてください。

開発現場での実装上のヒント

  • 再割り当て操作を行う際には、規模が大きなプログラムになる場合や複数の箇所で動的メモリ操作を行う場合、各操作ごとにエラー処理の実装を共有化する仕組みが役立ちます。
  • メモリ操作に関するログを残すことで、後からデバッグする際に原因を追いやすくなります。
  • 変数名や関数名は統一した命名規則に従い、コードの可読性を意識してください。
  • 定数としてサイズを管理する場合、変更が容易なようにマクロや定数変数を使用することが推奨されます。

以上が、開発環境で注意すべきポイントです。

まとめ

この記事ではC言語のrealloc関数を用いた動的メモリの再割り当て方法と、その利用シーン、基本構文、パラメータ、戻り値およびエラー処理について詳しく解説しました。

再割り当ての仕組みやエラー時の対処、メモリ拡大・縮小の注意点を理解することで、実践的な動的メモリ管理の知識が身につきます。

ぜひ、実際のコードを試して、再割り当ての動作を確認しながら新たな開発手法を取り入れてみてください。

関連記事

Back to top button
目次へ