0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C言語の動的メモリ管理入門|malloc・calloc・realloc・free・memset

0
Last updated at Posted at 2026-06-26

C言語を基礎から順に学びたい場合は、C言語 学習ロードマップ を参照してください。

前提として、構造体・typedef・enum・構造体配列を読んでいると、動的に構造体の配列を確保する例が理解しやすくなります。

配列を作るとき、これまでは次のように件数をコードへ直接書いていました。

int scores[5];

この書き方は、あらかじめ件数が分かっている場合に向いています。

一方で、実行してから入力された件数だけデータを扱いたい場合があります。

商品を何件登録するか、実行してから決める
↓
必要な件数だけメモリを用意する
↓
使い終わったら解放する

このようなときに使うのが、動的メモリ確保です。

この記事では、次の内容を扱います。

  • malloc でメモリを確保する
  • calloc でゼロのビット列で初期化されたメモリを確保する
  • realloc で確保済みメモリのサイズを変更する
  • free で確保したメモリを解放する
  • memset が何をする関数かを理解する
  • メモリリーク、二重解放、解放後アクセスを避ける

動的メモリは便利ですが、使い方を誤ると不具合や異常終了につながります。

そのため、使い方だけでなく「してはいけないこと」も一緒に確認します。


前提

この記事は、次の内容を学んだ人を対象にしています。

  • ポインタの基本
  • NULL の意味
  • 配列、構造体、構造体の配列
  • sizeof
  • 関数と戻り値

先に、次の記事を読んでください。

C言語のポインタ入門|「値」と「場所」を分けて考える

構造体、typedefenum、構造体の配列を学んでから進むと理解しやすくなります。

C言語でデータをまとめて扱う|構造体・typedef・enum・構造体配列


1. 動的メモリ確保とは

通常の配列は、プログラムを書く時点で件数を決めます。

int scores[5];

一方、動的メモリ確保では、プログラムの実行中に必要な件数を決められます。

プログラムを実行する
↓
「商品を何件登録しますか」と入力を受け取る
↓
入力された件数分のメモリを確保する
↓
商品データを使う
↓
使い終わったらメモリを解放する

動的に確保したメモリを扱う変数は、ポインタです。

int *scores = NULL;

この時点では、まだメモリを確保していません。

NULL は「どこも指していない」状態です。


2. malloc でメモリを確保する

malloc は、指定したサイズのメモリを確保する関数です。

使うには stdlib.h を読み込みます。

#include <stdlib.h>

たとえば、整数を5個入れられる領域を確保する場合は、次のように書きます。

int *scores = malloc(sizeof(int) * 5);

ただし、malloc は確保した領域に初期値を入れません。

確保直後の値を、そのまま使ってはいけません。

malloc で確保した直後
↓
中身は未初期化
↓
使う前に、必ず値を設定する

また、メモリの確保に失敗すると、mallocNULL を返します。

そのため、確保直後に必ず確認します。


malloc の基本例

malloc_scores.c というファイルを作成してください。

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

int main(void)
{
    // 整数を3個扱うことにします。
    int score_count = 3;

    // 整数3個分のメモリを確保します。
    // sizeof *scores は、scores が指す先の int 1個分のサイズです。
    int *scores = malloc(sizeof *scores * score_count);

    // 確保に失敗した場合は、以降の処理を行いません。
    if (scores == NULL)
    {
        printf("メモリを確保できませんでした。\n");

        return 1;
    }

    // malloc で確保した直後の値は使わず、先に値を設定します。
    scores[0] = 72;
    scores[1] = 88;
    scores[2] = 65;

    // 設定した値を表示します。
    for (int index = 0; index < score_count; index++)
    {
        printf("%d人目: %d点\n", index + 1, scores[index]);
    }

    // malloc で確保したメモリを解放します。
    free(scores);

    // 解放済みであることを明確にします。
    scores = NULL;

    return 0;
}

コンパイルして実行します。

gcc -std=c17 -Wall -Wextra -Wpedantic malloc_scores.c -o malloc_scores
./malloc_scores

sizeof *scores と書く理由

次の2つは、この例では同じ意味です。

malloc(sizeof(int) * score_count);
malloc(sizeof *scores * score_count);

sizeof *scores は、「scores が指している先の型1個分のサイズ」です。

ポインタの型を int * から別の型へ変えても、sizeof *scores ならサイズ指定を合わせやすくなります。

そのため、動的メモリ確保では次の形をよく使います。

Type *items = malloc(sizeof *items * item_count);

C言語では、malloc の戻り値をキャストしません。

// C言語では、このようなキャストは不要です。
int *scores = (int *)malloc(sizeof *scores * score_count);

キャストを付けると、#include <stdlib.h> の書き忘れによる警告を見えにくくすることがあります。


3. calloc でメモリを確保する

calloc は、指定した個数と1個分のサイズを受け取り、メモリを確保する関数です。

int *scores = calloc(score_count, sizeof *scores);

calloc は、確保した領域をすべてゼロのビット列で初期化します。

ただし、プログラムで使う値は、必要に応じて明示的に設定してください。

特にポインタを含む複雑な構造体では、「ゼロ初期化されているから、すべての意味で使える状態」と決めつけない方が安全です。


構造体の配列を calloc で確保する

calloc_products.c というファイルを作成してください。

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

// 商品の販売状態を表す型です。
typedef enum
{
    PRODUCT_STATUS_NOT_FOR_SALE = 0,
    PRODUCT_STATUS_ON_SALE = 1
} ProductStatus;

// 商品1件を表す型です。
typedef struct
{
    int id;
    char name[32];
    int unit_price;
    int stock_count;
    ProductStatus status;
} Product;

// 商品1件を表示する関数です。
void print_product(const Product *product)
{
    if (product == NULL)
    {
        return;
    }

    printf("商品ID: %d\n", product->id);
    printf("商品名: %s\n", product->name);
    printf("単価: %d円\n", product->unit_price);
    printf("在庫数: %d個\n", product->stock_count);
}

int main(void)
{
    // 今回は商品を3件扱います。
    size_t product_count = 3;

    // Product を3件分、動的に確保します。
    Product *products = calloc(product_count, sizeof *products);

    // 確保に失敗した場合は、以降の処理を行いません。
    if (products == NULL)
    {
        printf("メモリを確保できませんでした。\n");

        return 1;
    }

    // 1件目の商品情報を設定します。
    products[0] = (Product){
        101,
        "りんご",
        120,
        10,
        PRODUCT_STATUS_ON_SALE
    };

    // 2件目の商品情報を設定します。
    products[1] = (Product){
        102,
        "みかん",
        100,
        8,
        PRODUCT_STATUS_ON_SALE
    };

    // 3件目の商品情報を設定します。
    products[2] = (Product){
        103,
        "牛乳",
        210,
        5,
        PRODUCT_STATUS_NOT_FOR_SALE
    };

    // すべての商品を表示します。
    for (size_t index = 0; index < product_count; index++)
    {
        print_product(&products[index]);
        printf("\n");
    }

    // 確保したメモリを解放します。
    free(products);
    products = NULL;

    return 0;
}

4. realloc でメモリのサイズを変更する

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

たとえば、商品を3件分確保した後に、もう1件追加したい場合に使えます。

Product を3件分確保する
↓
4件目を追加したくなる
↓
realloc で4件分へ広げる
↓
4件目のデータを設定する

realloc で最も注意する点は、戻り値を元のポインタへ直接代入しないことです。

次の書き方は避けてください。

products = realloc(products, sizeof *products * new_product_count);

確保に失敗すると、reallocNULL を返します。

このとき、元の products を上書きすると、以前に確保していた領域を解放できなくなる可能性があります。

一時的なポインタへ受け取ってから、成功時だけ元のポインタへ代入します。


realloc の安全な基本形

Product *resized_products = realloc(
    products,
    sizeof *products * new_product_count);

if (resized_products == NULL)
{
    // products は、まだ元の確保済み領域を指しています。
    free(products);

    return 1;
}

products = resized_products;

商品を1件追加する例

realloc_product.c というファイルを作成してください。

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

typedef struct
{
    int id;
    char name[32];
    int unit_price;
} Product;

void print_product(const Product *product)
{
    if (product == NULL)
    {
        return;
    }

    printf("商品ID: %d, 商品名: %s, 単価: %d円\n",
        product->id,
        product->name,
        product->unit_price);
}

int main(void)
{
    // 最初は2件分のメモリを確保します。
    size_t product_count = 2;
    Product *products = calloc(product_count, sizeof *products);

    if (products == NULL)
    {
        printf("メモリを確保できませんでした。\n");

        return 1;
    }

    products[0] = (Product){ 101, "りんご", 120 };
    products[1] = (Product){ 102, "みかん", 100 };

    // 3件目を追加するため、新しい件数を決めます。
    size_t new_product_count = product_count + 1;

    // 一時的なポインタで realloc の結果を受け取ります。
    Product *resized_products = realloc(
        products,
        sizeof *products * new_product_count);

    if (resized_products == NULL)
    {
        // この時点では products は元の領域を指しているため、解放できます。
        free(products);
        products = NULL;

        printf("メモリを広げられませんでした。\n");

        return 1;
    }

    // サイズ変更に成功したため、ポインタと件数を更新します。
    products = resized_products;
    product_count = new_product_count;

    // 追加した3件目の商品を設定します。
    products[2] = (Product){ 103, "牛乳", 210 };

    for (size_t index = 0; index < product_count; index++)
    {
        print_product(&products[index]);
    }

    free(products);
    products = NULL;

    return 0;
}

5. free で必ずメモリを解放する

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

free(products);
products = NULL;

free を忘れることを、メモリリークと呼びます。

メモリを確保する
↓
使い終わる
↓
free しない
↓
プログラムが動いている間、不要なメモリを使い続ける

短時間で終わる小さなプログラムでは目立たなくても、長時間動くプログラムや繰り返し処理では問題になります。

確保した場所と解放する場所を意識してください。

malloc / calloc / realloc で確保した
↓
最後に free する責任がある

解放後のポインタは使わない

次のコードは危険です。

free(products);

printf("%d\n", products[0].id);

free の後、その領域は自分のものではありません。

解放後にアクセスすることを、解放後アクセスと呼びます。

free の直後に NULL を代入すると、解放済みであることが分かりやすくなります。

free(products);
products = NULL;

NULL のポインタを free しても問題ありません。

free(products);

そのため、後片付けの処理を書きやすくなります。


同じ領域を2回 free しない

次のように、同じ領域を2回解放してはいけません。

free(products);
free(products);

これを二重解放と呼びます。

free の後に products = NULL; としておけば、2回目は free(NULL) になるため、二重解放を避けやすくなります。

free(products);
products = NULL;

free(products);

ただし、別のポインタが同じ領域を指している場合は、NULL を代入しただけでは防げません。

「どの処理がメモリを解放する責任を持つか」を決めることが重要です。


6. memset はメモリをバイト単位で埋める関数

memset は、指定したメモリ領域を1バイト単位で同じ値に埋める関数です。

使うには string.h を読み込みます。

#include <string.h>

文字列用の配列を空に戻す例です。

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

int main(void)
{
    char message[32] = "前のメッセージ";

    printf("変更前: %s\n", message);

    // message の全領域を 0 で埋めます。
    memset(message, 0, sizeof message);

    // 先頭が文字列の終わりを表す 0 になったため、空文字列です。
    printf("変更後: %s\n", message);

    return 0;
}

memset の形は次のとおりです。

memset(対象の場所, 埋める値, 埋めるバイト数);
memset(message, 0, sizeof message);

これは、message の領域全体をゼロで埋めます。


memset を初期化の万能手段にしない

memset は便利ですが、構造体やポインタを「何となく全部ゼロにする」ための万能な初期化関数ではありません。

特に、初期値に意味があるデータでは、値を明示的に設定する方が読みやすく安全です。

Product product = {
    101,
    "りんご",
    120,
    10
};

動的に確保した構造体の配列も、必要な値を設定してから使います。

products[0].id = 101;
products[0].unit_price = 120;

memset は、文字列用バッファや通信データのバッファなど、バイト列を扱う場面で意図を理解して使ってください。


7. 動的メモリ管理で守ること

動的メモリ管理では、次の3点を必ず守ってください。

確保に失敗していないか NULL を確認する
↓
確保したメモリは、使い終わったら free する
↓
free した後は、そのポインタを使わない

よくある問題を整理します。

問題 何が起きるか 防ぎ方
確保失敗を確認しない NULL を使って異常終了する可能性がある malloc などの直後に確認する
free し忘れ メモリリークになる 確保した処理と解放する処理を対応させる
二重解放 異常終了やデータ破壊につながる 解放責任を決め、解放後に NULL を入れる
解放後アクセス 自分のものではない領域を使ってしまう free 後はアクセスしない
realloc を直接代入 失敗時に元の領域を見失う 一時ポインタで戻り値を受け取る

8. 確認課題

課題1:点数の件数を入力して確保する

キーボードから点数の件数を入力し、その件数分の int 型配列を calloc で確保してください。

確保に成功したら、すべての点数へ 0 を入れて表示してください。

答えを見る
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int score_count = 0;

    printf("点数の件数を入力してください: ");

    if (scanf("%d", &score_count) != 1 || score_count <= 0)
    {
        printf("1以上の整数を入力してください。\n");

        return 1;
    }

    int *scores = calloc(score_count, sizeof *scores);

    if (scores == NULL)
    {
        printf("メモリを確保できませんでした。\n");

        return 1;
    }

    for (int index = 0; index < score_count; index++)
    {
        printf("%d人目: %d点\n", index + 1, scores[index]);
    }

    free(scores);
    scores = NULL;

    return 0;
}

課題2:realloc で点数を1件追加する

課題1で確保した配列を、realloc を使って1件増やしてください。

realloc の戻り値は、元のポインタへ直接代入せず、一時的なポインタで受け取ります。

答えの例を見る
int new_score_count = score_count + 1;

int *temporary_scores = realloc(scores, sizeof *scores * new_score_count);

if (temporary_scores == NULL)
{
    printf("メモリを拡張できませんでした。\n");
    free(scores);
    scores = NULL;

    return 1;
}

scores = temporary_scores;
scores[score_count] = 0;
score_count = new_score_count;

realloc に失敗した場合でも、元の scores は解放されません。

そのため、失敗時は元の scores を使って free できます。


まとめ

動的メモリ管理では、実行時に必要な件数だけメモリを用意できます。

関数 役割
malloc 指定したサイズのメモリを確保する。中身は未初期化
calloc 指定した個数・サイズのメモリを確保し、ゼロのビット列で初期化する
realloc 確保済みメモリのサイズを変更する
free 確保したメモリを解放する
memset 指定した領域を1バイト単位で同じ値に埋める

最も重要なのは、次の流れです。

確保する
↓
NULL を確認する
↓
必要な値を設定して使う
↓
使い終わったら free する
↓
解放後は使わない

次は、構造体で扱ったデータをファイルへ保存し、ヘッダーファイルと複数の .c ファイルへ分ける方法を学びます。


次の記事

次は、プログラムのデータをファイルへ保存し、後で読み込む方法を学びます。

あわせて、.h ファイルと複数の .c ファイルへ処理を分ける基本も確認します。

C言語でデータを保存する|ファイル操作・ヘッダーファイル・複数ファイルへの分割

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?