C言語のきほん|動的メモリの確保と解放

以下、親しみやすいやわらかい口調で、そのまま記事に使いやすい形に整えてまとめます。ご提示いただいた文書の内容をもとに、サンプルプログラムは別例へ差し替え、実践問題とチャレンジ問題に類似した問題も新しく追加しています。

必要な分だけメモリを使うしくみがわかると、C言語はもっと自由になる

C言語では、配列を使ってデータをまとめて扱うことができます。
ただ、通常の配列は、プログラムを書く時点で大きさを決めておかなければなりません。

たとえば、5人分の点数を扱う、100文字分の文字列を入れる、といったように最初からサイズが決まっている場合は固定長の配列で十分です。
しかし実際のプログラムでは、実行してみるまで必要な個数が分からないこともよくあります。

  • ユーザーが何件データを入力するか分からない
  • 後から配列を大きくしたくなる
  • 必要な分だけメモリを使って無駄を減らしたい

このような場面で使うのが、動的メモリ確保です。

動的メモリ確保では、プログラムの実行中に必要なサイズのメモリを用意し、不要になったら解放します。
この仕組みを使うことで、固定長の配列では難しい柔軟な処理ができるようになります。

C言語では、主に次の3つの関数を使って動的メモリを管理します。

関数役割
malloc指定サイズのメモリを新しく確保する
reallocすでに確保したメモリのサイズを変更する
free確保したメモリを解放する

この3つはセットで理解することがとても大切です。
確保するだけで終わりではなく、最後にきちんと解放するところまで含めて、はじめて正しい使い方になります。

malloc関数の基本

malloc は、必要な大きさのメモリ領域をヒープ領域から確保する関数です。

#include <stdlib.h>
void *malloc(size_t size);

size には、確保したいバイト数を指定します。
返り値は、確保した領域の先頭アドレスです。型は void * なので、使いたい型のポインタへキャストして使います。
なお、メモリの確保に失敗した場合は NULL が返ります。

ここで大事なのは、malloc で確保された直後の領域の中身は不定だということです。
つまり、何が入っているか分かりません。使う前には、自分で値を代入して初期化する必要があります。

たとえば、int 型のデータを 5 個分だけ扱いたいときは、次のように書けます。

int count = 5;
int *data = (int *)malloc(count * sizeof(int));
if (data == NULL) {
    printf("メモリ確保に失敗しました。\n");
    return 1;
}

count * sizeof(int) としているのは、int を 5 個分確保するためです。
sizeof(int) は環境によって変わる可能性があるので、バイト数を直接書くのではなく sizeof を使う書き方が安全です。

mallocの流れをつかむサンプル

ここでは、テストの点数を人数分だけ動的に確保して扱ってみます。

ファイル名:14_2_1.c

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int student_count = 4;

    /* 学生4人分の点数を保存する領域を確保する */
    int *scores = (int *)malloc(student_count * sizeof(int));
    if (scores == NULL) {
        printf("点数データ用のメモリ確保に失敗しました。\n");
        return 1;
    }

    /* 点数を設定する */
    scores[0] = 78;
    scores[1] = 85;
    scores[2] = 92;
    scores[3] = 88;

    printf("登録した点数一覧\n");
    for (int i = 0; i < student_count; i++) {
        printf("%d人目: %d点\n", i + 1, scores[i]);
    }

    /* 使い終わったので解放する */
    free(scores);

    return 0;
}

実行結果例

登録した点数一覧
1人目: 78点
2人目: 85点
3人目: 92点
4人目: 88点

このプログラムでは、学生数に合わせて必要な分だけメモリを確保しています。
固定長配列でも似たことはできますが、動的メモリを使うと、人数があとから変わるような場面にも対応しやすくなります。

free関数の役割

malloc で確保したメモリは、使い終わったら free で解放します。

#include <stdlib.h>
void free(void *ptr);

free は、malloc や realloc で確保した領域を解放し、再利用できる状態に戻す関数です。
返り値はありません。

free を忘れると、使い終わったはずのメモリが解放されずに残ってしまいます。
これをメモリリークといいます。

特に、長く動き続けるプログラムや、何度も繰り返し確保を行うプログラムでは、メモリリークが積み重なると大きな問題になります。
そのため、動的メモリを使うときは、

確保したら、最後に必ず解放する

という習慣がとても大切です。

また、free した後のポインタをそのまま使うのは危険です。
解放済みの領域にアクセスすると、予測できない動作の原因になります。
必要に応じて、

scores = NULL;

のようにして、もう使えないことを明示する書き方もよく使われます。

realloc関数の基本

realloc は、すでに確保しているメモリ領域のサイズを変更するための関数です。

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

ptr には、すでに malloc や realloc で確保した領域へのポインタを渡します。
size には、新しく必要なサイズを指定します。
成功すれば、新しい領域の先頭アドレスが返ります。失敗した場合は NULL が返ります。

realloc の大事なポイントは、場所が変わることがあるという点です。
サイズ変更の結果、元の場所のままで広げられない場合は、別の場所に新しい領域が確保され、元の内容が可能な範囲でコピーされます。

そのため、次のような書き方には注意が必要です。

array = (int *)realloc(array, new_size * sizeof(int));

この書き方だと、もし realloc に失敗して NULL が返ったときに、元のアドレスを失ってしまいます。
すると、元の領域を free できなくなってしまいます。

そこで、安全のために一時ポインタを使います。

int *temp = (int *)realloc(array, new_size * sizeof(int));
if (temp == NULL) {
    printf("メモリ再確保に失敗しました。\n");
    free(array);
    return 1;
}
array = temp;

この形なら、失敗しても元の array を失わずに済みます。

reallocを使ったサイズ変更のサンプル

ここでは、最初は3件分の商品価格を保存し、あとから6件分へ増やす例に変更してみます。

ファイル名:14_2_2.c

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int initial_count = 3;
    int new_count = 6;

    /* 最初に3件分の価格データを確保する */
    int *prices = (int *)malloc(initial_count * sizeof(int));
    if (prices == NULL) {
        printf("価格データ用のメモリ確保に失敗しました。\n");
        return 1;
    }

    /* 初期データを入れる */
    prices[0] = 120;
    prices[1] = 250;
    prices[2] = 380;

    printf("初期の商品価格\n");
    for (int i = 0; i < initial_count; i++) {
        printf("%d円 ", prices[i]);
    }
    printf("\n");

    /* 6件分へ拡張する */
    int *temp = (int *)realloc(prices, new_count * sizeof(int));
    if (temp == NULL) {
        printf("メモリ再確保に失敗しました。\n");
        free(prices);
        return 1;
    }
    prices = temp;

    /* 拡張された部分に新しい価格を入れる */
    prices[3] = 410;
    prices[4] = 560;
    prices[5] = 720;

    printf("拡張後の商品価格\n");
    for (int i = 0; i < new_count; i++) {
        printf("%d円 ", prices[i]);
    }
    printf("\n");

    free(prices);

    return 0;
}

実行結果例

初期の商品価格
120円 250円 380円
拡張後の商品価格
120円 250円 380円 410円 560円 720円

この例では、最初に3件分だけ用意しておき、あとから必要になった分だけサイズを拡大しています。
最初から大きな配列を用意することもできますが、必要な分だけ広げるほうが、柔軟で無駄の少ない設計になります。

malloc、realloc、freeの関係

この3つの関数の関係を整理すると、次の流れになります。

手順使う関数内容
1malloc最初に必要なサイズの領域を確保する
2realloc必要に応じてサイズを変更する
3free使い終わったら解放する

この流れを意識しておくと、動的メモリの処理がかなり整理しやすくなります。

特に初心者のうちは、次の2つを必ず意識するとよいです。

  • 確保に失敗していないか、NULL を確認する
  • 最後に free を忘れない

この2つだけでも、かなり安全なプログラムになります。

malloc と free の基本イメージ

  • 必要な個数が決まったら malloc でヒープに領域を確保する
  • ポインタ変数は、その先頭アドレスを保持する
  • 使い終わったら free で解放する

この図では、ポインタ変数そのものがデータ本体を持っているわけではなく、
ヒープに確保された領域の先頭アドレスを指していることを視覚的に表せます。

配列のように使っていても、実際にはヒープ上の領域をポインタ経由で扱っている、という点が見えると理解しやすくなります。

realloc によるサイズ変更

  • 最初は小さい領域を確保する
  • データ数が増えたら realloc で拡張する
  • 場合によっては別の場所へ移動することがある

realloc は、単純に今の場所を広げるだけとは限りません。
別の場所に新しい領域を確保して、そこへ内容を移すことがあります。
そのため、一時ポインタを使った安全な受け取り方が必要になります。

動的メモリを使うときの注意点

動的メモリ確保は便利ですが、いくつか注意点があります。

注意点内容
NULLチェックをする確保に失敗する可能性がある
初期化されていないmalloc直後の中身は不定
freeを忘れないメモリリークを防ぐため
free後に使わない解放済み領域へのアクセスは危険
reallocは一時変数で受ける失敗時に元の領域を失わないため

このあたりは、実務でもとても大切な基本です。
最初のうちから丁寧に書く習慣をつけておくと、あとで大きな力になります。

実践問題

次の手順で、入力された個数分の整数を保存し、合計と平均を表示するプログラムを作成してください。

①整数を何個入力するかを入力する
②その個数分の int 型配列を malloc で動的に確保する
③for 文で整数を順に入力する
④入力された整数の合計と平均を計算する
⑤結果を表示する
⑥最後に free で解放する

実行結果例

何個の整数を入力しますか? 5
1個目の整数を入力してください: 10
2個目の整数を入力してください: 20
3個目の整数を入力してください: 30
4個目の整数を入力してください: 40
5個目の整数を入力してください: 50
合計: 150
平均: 30.00

解答例

ファイル名:14_2_3.c

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int count;
    int sum = 0;

    printf("何個の整数を入力しますか? ");
    scanf("%d", &count);

    if (count <= 0) {
        printf("1以上の個数を入力してください。\n");
        return 1;
    }

    /* 入力個数分の領域を確保する */
    int *numbers = (int *)malloc(count * sizeof(int));
    if (numbers == NULL) {
        printf("メモリ確保に失敗しました。\n");
        return 1;
    }

    /* 整数を順に入力する */
    for (int i = 0; i < count; i++) {
        printf("%d個目の整数を入力してください: ", i + 1);
        scanf("%d", &numbers[i]);
        sum += numbers[i];
    }

    printf("合計: %d\n", sum);
    printf("平均: %.2f\n", (double)sum / count);

    free(numbers);

    return 0;
}

解説

この問題では、入力個数が実行時に決まるため、固定長配列ではなく動的メモリ確保がぴったりです。

まず count を読み取り、その値をもとに必要な分だけ int 型配列の領域を確保しています。
もし count が 5 なら int を 5 個分、10 なら 10 個分というように、必要量に応じてサイズが変わります。

そして、numbers[i] の形で通常の配列のように扱える点も大切です。
ポインタで受け取っていても、動的に確保した連続領域は添字でアクセスできます。

最後の free(numbers); まで書いてあることで、確保から解放までひととおりの流れを練習できます。

実践問題

次の手順で、複数の商品名をカンマ区切りの1つの文字列にまとめるプログラムを作成してください。

①商品名はポインタの配列で管理する
②最初に小さめの文字配列領域を malloc で動的に確保する
③各商品名を順に追加していく
④容量が足りなくなりそうなら realloc で拡張する
⑤最終的に 商品一覧: りんご, みかん, ぶどう のような形式で表示する
⑥最後に free で解放する

実行結果例

商品一覧: りんご, みかん, ぶどう, もも, なし

解答例

ファイル名:14_2_4.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ITEM_COUNT 5

int main(void)
{
    char *items[ITEM_COUNT] = {
        "りんご", "みかん", "ぶどう", "もも", "なし"
    };

    size_t capacity = 32;
    char *buffer = (char *)malloc(capacity);
    if (buffer == NULL) {
        printf("メモリ確保に失敗しました。\n");
        return 1;
    }

    buffer[0] = '\0';

    for (int i = 0; i < ITEM_COUNT; i++) {
        size_t needed = strlen(buffer) + strlen(items[i]) + 3;

        if (i == 0) {
            needed = strlen(buffer) + strlen(items[i]) + 1;
        }

        if (needed > capacity) {
            size_t new_capacity = capacity * 2;
            while (needed > new_capacity) {
                new_capacity *= 2;
            }

            char *temp = (char *)realloc(buffer, new_capacity);
            if (temp == NULL) {
                printf("メモリ再確保に失敗しました。\n");
                free(buffer);
                return 1;
            }

            buffer = temp;
            capacity = new_capacity;
        }

        if (i > 0) {
            strcat(buffer, ", ");
        }
        strcat(buffer, items[i]);
    }

    printf("商品一覧: %s\n", buffer);

    free(buffer);

    return 0;
}

解説

この問題は、文字列を連結しながら、必要に応じてメモリを拡張する練習になります。

最初に十分大きいサイズを決め打ちするのではなく、まずは小さく確保して、足りなくなりそうなら realloc で広げています。
この考え方は、可変長のテキスト処理やログ生成などでもよく使われます。

ポイントは、追加前に必要な長さを見積もっているところです。
今の buffer の長さ、追加する商品名の長さ、区切り文字 , の長さ、そして終端文字を考慮して、容量が足りるか確認しています。

realloc の結果は temp で受け取り、成功したときだけ buffer に代入しています。
ここも安全な書き方としてとても大事です。

文字列バッファの拡張イメージ

  • 最初は小さいバッファを malloc で確保する
  • 商品名を追加していく
  • 容量不足になりそうなら realloc で大きくする
  • 最後に1本の文字列が完成する

この図では、文字列を少しずつ追加していくと、最初に確保した領域では足りなくなることがある、という流れを表せます。
そして、そのときに realloc を使えば、より大きな領域へ広げて処理を続けられることが分かります。

固定長ではなく、必要に応じて伸ばしていく考え方が、動的メモリ確保の大きな魅力です。

学習のポイント

ポイント内容
malloc必要なサイズのメモリを新しく確保する
realloc確保済みメモリのサイズを変更する
free使い終わったメモリを解放する
NULLチェック確保失敗に備えて必ず確認する
初期化malloc直後の値は不定なので自分で設定する
安全なreallocいったん temp で受け取る
解放の習慣確保したら最後に必ず free する

動的メモリ確保は、C言語らしさがよく表れる重要なテーマです。
最初は少し難しく感じるかもしれませんが、malloc で確保する、必要なら realloc で広げる、最後に free で解放する、という流れを一つずつ丁寧に追っていけば、きちんと理解できるようになります。