6日でできる 新C言語入門|C言語のmallocとfreeの使い方

必要な分だけ確保して、使い終わったらきちんと返す。
mallocとfreeを理解すると、C言語のメモリ管理がぐっと実践的になります。

C言語では、プログラムを動かすときにメモリをどのように使うかがとても重要です。特に、実行してみないと必要なデータ量が分からない場面では、あらかじめ固定サイズの配列を用意するだけでは不十分になることがあります。

そんなときに活躍するのが、動的メモリ確保です。
動的メモリ確保を使うと、プログラムの実行中に必要なサイズのメモリを確保し、不要になったタイミングで解放できます。これによって、可変長の配列を扱ったり、一時的に大きなデータを保持したりといった柔軟な処理が可能になります。

ただし、便利な一方で注意点もあります。確保したメモリを解放し忘れると、メモリリークの原因になりますし、確保に失敗したのにそのまま使ってしまうと、プログラムが異常終了することもあります。

ここでは、mallocとfreeを中心に、C言語の動的メモリ管理の基本から実践的な使い方まで、順を追ってていねいに見ていきます。あわせて、ヒープ領域の意味、NULLチェックの重要性、メモリリークの防ぎ方も整理していきましょう。

メモリ領域の基本を知っておこう

C言語の動的メモリを理解するには、まずプログラムがどのようなメモリ領域を使っているのかをざっくり知っておくと分かりやすいです。

プログラム実行時のメモリは、大まかに次のような領域に分けて考えられます。

名前主な内容
プログラム領域実行される機械語の命令が置かれる領域
静的領域グローバル変数やstatic変数が置かれる領域
ヒープ領域mallocなどで動的に確保する領域
スタック領域関数のローカル変数や戻り先情報などが置かれる領域

この中で、mallocやfreeと深く関係するのがヒープ領域です。

スタック領域の変数は、関数が終わると自動的に片付けられます。たとえば、関数内で宣言した int x; のようなローカル変数は、その関数の実行が終われば使われなくなります。

一方、ヒープ領域のメモリは、プログラマが自分で確保し、自分で解放する必要があります。ここがC言語らしい重要ポイントです。

図:C言語プログラムの主なメモリ領域

この図から分かること

この図から分かるのは、C言語のプログラムが複数のメモリ領域を使って動いており、mallocで確保するメモリはヒープ領域に置かれるという点です。

ローカル変数は主にスタック領域に置かれ、関数が終わると自動的に整理されます。しかし、ヒープ領域に確保したメモリは自動では片付かないため、freeで明示的に解放しなければなりません。ここを理解しておくと、動的メモリ管理の考え方がかなりつかみやすくなります。

ヒープ領域を使うと何が便利なのか

ヒープ領域を使う最大の利点は、実行時に必要なサイズを決められることです。

たとえば、ユーザーが入力する件数が実行してみないと分からない場合、固定長配列だけでは対応しづらいことがあります。
そのようなときに、必要な個数だけ int型の配列を動的に確保すれば、無駄の少ないメモリの使い方ができます。

ヒープ領域を使うメリットを整理すると、次のようになります。

利点内容
実行時にサイズを決められる入力内容に応じて必要な分だけ確保できる
大きなデータを扱いやすい固定長配列に頼らず柔軟に設計できる
データ構造の自由度が高いリスト、木構造、可変長バッファなどに応用できる

一方で、注意点もあります。

注意点内容
解放忘れfreeしないとメモリリークになる
二重解放同じ領域を2回freeすると危険
NULLチェック不足malloc失敗時にそのまま使うと不正動作の原因になる
解放後のアクセスfree後のポインタを使うと未定義動作になる

便利だからこそ、ルールを守って使うことが大切です。

mallocとfreeの役割を整理しよう

動的メモリ管理でまず覚えたい代表的な関数は次の4つです。

関数名役割
malloc指定したバイト数のメモリを確保する
calloc指定個数分のメモリを確保し、0で初期化する
reallocすでに確保したメモリのサイズを変更する
free確保したメモリを解放する

この記事の中心となるのは malloc と free です。

mallocは、必要なサイズのメモリをヒープ領域から借りるイメージです。
freeは、借りたメモリをヒープ領域へ返すイメージです。

基本の流れはとてもシンプルです。

  1. 必要なサイズを考える
  2. mallocで確保する
  3. 戻り値がNULLでないか確認する
  4. 配列やデータとして使う
  5. 使い終わったらfreeで解放する

この流れを習慣にすると、かなり安全に書けるようになります。

mallocの書き方と考え方

mallocは、必要なバイト数を引数に取ります。

たとえば int型を5個分確保したいときは、1個あたりのサイズに5を掛けます。

malloc(sizeof(int) * 5)

sizeof(int) は int型1個のバイト数です。
環境にもよりますが、多くの場合4バイトです。

つまり、上の式は int型5個分のメモリを確保する意味になります。

mallocの戻り値は、確保した領域の先頭アドレスです。これをポインタ変数で受け取ります。

意味
malloc(sizeof(int) * 5)int型5個分のメモリを確保する
(int*)malloc(sizeof(int) * 5)戻り値をint型ポインタとして扱う
nums = (int*)malloc(sizeof(int) * 5)確保した先頭アドレスをnumsへ代入する

そして最も大事なのが、確保に失敗した場合は NULL が返ることです。
そのため、mallocの直後には必ずNULLチェックを入れるのが基本です。

mallocとfreeの基本例を見てみよう

まずはシンプルな例で、動的配列の基本を確認してみましょう。

プロジェクト/ファイル名: Lesson59_1/main.c

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

int main(void) {
    int* scores = NULL;
    int i;
    int count = 6;

    // int型6個分のメモリを動的に確保する
    scores = (int*)malloc(sizeof(int) * count);

    // 確保に失敗した場合はNULLが返る
    if (scores == NULL) {
        printf("メモリの確保に失敗しました。\n");
        return 1;
    }

    // 動的に確保した配列へ値を代入する
    for (i = 0; i < count; i++) {
        scores[i] = (i + 1) * 5;
    }

    // 配列の内容を表示する
    printf("動的に確保した配列の内容:");
    for (i = 0; i < count; i++) {
        printf("%d ", scores[i]);
    }
    printf("\n");

    // 合計値を計算して表示する
    int sum = 0;
    for (i = 0; i < count; i++) {
        sum += scores[i];
    }
    printf("合計値:%d\n", sum);

    // 使い終わったメモリを解放する
    free(scores);
    scores = NULL;

    return 0;
}

実行結果例

動的に確保した配列の内容:5 10 15 20 25 30
合計値:105

このプログラムの流れは次の通りです。

処理内容
ポインタをNULLで初期化まだ何も指していない状態を明確にする
mallocで確保int型6個分の領域をヒープに用意する
NULLチェック確保失敗を見逃さない
値を代入通常の配列のように scores[i] で扱える
freeで解放使い終わった領域を返す
NULLを再代入解放後の誤使用を防ぎやすくする

ここで大切なのは、動的に確保したメモリも、使っている間は普通の配列のように添字で扱えるということです。
scores[0] や scores[1] のようにアクセスできます。

freeは必ず対応させて考える

mallocを学び始めたばかりのときは、確保する方ばかりに意識が向きがちです。ですが、実務でも学習でも本当に大切なのは free を忘れないことです。

freeは、mallocで確保したメモリを解放する関数です。

free(scores);

これで、scoresが指していたヒープ領域は解放されます。
ただし、freeしたあとに scores という変数自体が消えるわけではありません。
ポインタ変数 scores はまだ存在していますが、その指し先はもう有効ではありません。

そのため、freeのあとに次のようにNULLを代入しておく習慣がよく使われます。

scores = NULL;

これをしておくと、あとで誤って使ってしまったときにも、少なくとも「どこも指していない」状態が明確になります。

図:mallocからfreeまでの基本的な流れ

この図から分かること

この図から分かるのは、動的メモリ管理は単にmallocを書くことではなく、NULLで初期化し、確保し、確認し、使い、最後に解放するまでが一連の流れだということです。

特に、NULLチェックとfreeによる解放は安全なプログラムを書くうえで欠かせません。さらに、freeのあとにNULLを再代入しておくと、解放後の誤使用を防ぎやすくなります。

メモリリークとは何か

メモリリークは、動的に確保したメモリを解放しないまま使えなくしてしまう状態のことです。

たとえば、mallocで確保した領域のアドレスを持っていたポインタを別の値で上書きしてしまったり、freeせずに関数を抜けてしまったりすると、そのメモリはもう使えないのにヒープ領域には残り続けます。

これが積み重なると、使えるメモリが減っていきます。短時間で終わる小さなプログラムでは目立たないこともありますが、長時間動作するプログラムや繰り返し確保を行うプログラムでは深刻な問題になります。

用語意味
メモリリーク不要なのに解放されないヒープメモリが残ること
NULL有効なアドレスを持っていないことを示す値
ダングリングポインタfree後も古いアドレスを持ったままの危険なポインタ

よくある原因は次の通りです。

原因
freeの書き忘れ処理が終わったのに解放していない
途中でreturnしてしまうエラー処理の分岐で解放前に終了する
ポインタの上書き先に確保した領域のアドレスを失ってしまう
複雑な制御複数の分岐で解放漏れが起きる

C言語では、使い終わったら必ず解放するという意識がとても大切です。

実行時にサイズが決まる配列の例

動的メモリ確保が特に役立つのは、実行時にサイズが決まる配列です。
ここでは、ユーザーに要素数を入力してもらい、その個数分の配列を動的に作る例を見てみましょう。

プロジェクト/ファイル名: Lesson59_2/main.c

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

int main(void) {
    int len;
    int i;
    int* values = NULL;
    int total = 0;

    printf("配列の長さを入力してください:");
    scanf("%d", &len);

    // 1未満の長さは不正入力として扱う
    if (len <= 0) {
        printf("配列の長さは1以上にしてください。\n");
        return 0;
    }

    // 入力された長さに応じてメモリを動的に確保する
    values = (int*)malloc(sizeof(int) * len);

    // 確保に失敗した場合のチェック
    if (values == NULL) {
        printf("メモリの生成に失敗しました。\n");
        return 1;
    }

    // 乱数を代入して表示する
    srand((unsigned int)time(NULL));
    printf("生成した配列:");
    for (i = 0; i < len; i++) {
        values[i] = rand() % 10 + 1;
        total += values[i];
        printf("%d ", values[i]);
    }
    printf("\n");

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

    // 使用後は必ず解放する
    free(values);
    values = NULL;

    return 0;
}

実行結果例1

配列の長さを入力してください:7
生成した配列:4 9 2 7 1 8 5
合計値:36
平均値:5.14

実行結果例2

配列の長さを入力してください:0
配列の長さは1以上にしてください。

実行結果例3

配列の長さを入力してください:999999999
メモリの生成に失敗しました。

このプログラムでは、固定長配列ではなく、入力された長さに応じて配列を作っています。
このように、実行時に必要量が決まる場面では malloc がとても便利です。

また、この例では次の点も重要です。

  • 入力値が1以上かを先に確認している
  • mallocの結果をNULLチェックしている
  • 使い終わったらfreeしている
  • 平均値の計算まで行い、確保した配列を実際の処理に活用している

学習用のサンプルとしても、かなり実践的な形です。

なぜNULLチェックが必要なのか

mallocは必ず成功するとは限りません。
メモリ不足や、確保しようとしたサイズが大きすぎる場合には、確保に失敗して NULL を返します。

もしNULLチェックをせずに、そのまま values[0] のように使おうとすると、不正なメモリアクセスになり、プログラムが異常終了する可能性があります。

たとえば、次のような流れが安全です。

手順内容
1ポインタをNULLで初期化する
2mallocで必要量を確保する
3戻り値がNULLか確認する
4問題なければ使用する
5使用後にfreeする

この手順は、配列でも構造体でもほぼ共通です。
動的メモリを扱うなら、NULLチェックは必須だと考えておくとよいです。

mallocで確保した領域は初期化されていない

mallocで確保したメモリ領域は、中身が自動で0になるわけではありません。
何が入っているか分からない状態、つまり不定の値が入っている可能性があります。

そのため、確保したら必要に応じて自分で初期化することが大切です。

たとえば、0で初期化したいなら、for文で代入する方法があります。

for (i = 0; i < len; i++) {
    values[i] = 0;
}

あるいは、0初期化を前提にしたい場合は calloc を使う選択肢もあります。

関数特徴
malloc確保だけを行う。中身は未初期化
calloc確保と同時に0初期化される

ただし、mallocとfreeの基本を学ぶ段階では、まず malloc は未初期化、だから必要なら自分で初期化する、と覚えておくのが分かりやすいです。

freeのあとに気を付けること

freeを書いたからそれで完全に安全、というわけではありません。
freeしたあとにも注意点があります。

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

freeしたあとに、そのポインタで配列アクセスや参照をしてはいけません。

free(values);
/* このあと values[0] を使うのは危険 */

free後の領域は、もう自分のものではありません。
そこへアクセスすると未定義動作になります。

同じメモリを2回freeしない

次のような書き方も危険です。

free(values);
free(values);

同じ領域を2回解放すると、プログラムが不安定になる可能性があります。

free後はNULLを代入すると扱いやすい

次のようにしておくと、誤って再利用するリスクを下げやすくなります。

free(values);
values = NULL;

この習慣はとてもおすすめです。

図:メモリリークと正しい解放のイメージ

この図から分かること

この図から分かるのは、動的に確保したメモリを解放しないと、不要なのに残り続けるメモリが増えてしまうということです。これがメモリリークです。

一方、正しくfreeを使えば、使い終わったメモリをヒープ領域へ返せます。さらに、free後にNULLを代入しておけば、解放済みメモリへの誤アクセスを防ぎやすくなります。

mallocとfreeを使うときの定番パターン

動的メモリ管理では、次の書き方を基本形として覚えておくと便利です。

型* ポインタ名 = NULL;

ポインタ名 = (型*)malloc(sizeof(型) * 個数);
if (ポインタ名 == NULL) {
    /* エラー処理 */
}

/* 必要な処理 */

free(ポインタ名);
ポインタ名 = NULL;

この形をそのまま使える場面はとても多いです。
配列、構造体、文字列バッファなどにも応用できます。

よくあるミスも押さえておこう

動的メモリ管理では、初学者がつまずきやすいポイントがあります。

よくあるミス内容
freeを書き忘れるメモリリークの原因になる
NULLチェックをしない確保失敗時に危険
必要サイズの計算を間違える領域不足で不正アクセスの原因になる
free後に再利用するダングリングポインタになる
同じポインタを二重解放する不安定な動作の原因になる

特に、必要サイズの計算は丁寧に書くことが大切です。
int型10個なら sizeof(int) * 10、double型20個なら sizeof(double) * 20 のように、型に応じて正しく計算します。

callocやreallocも知っておくと便利

この記事の中心は malloc と free ですが、周辺の関数も軽く知っておくと見通しがよくなります。

calloc

callocは、指定した個数分のメモリを確保し、さらに0で初期化してくれます。

arr = (int*)calloc(len, sizeof(int));

0初期化が必要な配列では便利です。

realloc

reallocは、すでに確保したメモリのサイズを変更したいときに使います。

arr = (int*)realloc(arr, sizeof(int) * new_len);

ただし、reallocは少し扱いが難しいため、まずは malloc と free の基本をしっかり理解してから使うのがおすすめです。

実践で意識したい書き方

学習段階では、動的メモリを使うたびに次の3点を意識するとかなり安定します。

必要サイズを明確にする

何個分、何バイト分を確保するのかをはっきりさせます。
曖昧なまま書くと、サイズ不足や無駄な確保につながります。

エラー処理を省略しない

小さなサンプルでも NULL チェックを入れる習慣をつけておくと、あとで大きなプログラムを書いたときに役立ちます。

解放までをセットで考える

mallocを書いたら、そのメモリをどこでfreeするかも同時に考えるようにします。
確保と解放を1つのセットとして考えるのが大切です。

学習を次につなげるために

mallocとfreeが分かるようになると、C言語でできることがかなり広がります。

たとえば次のようなテーマにつながっていきます。

  • 可変長の配列処理
  • 文字列バッファの動的生成
  • 構造体の動的生成
  • 単方向リストや木構造などのデータ構造
  • reallocを使ったサイズ変更
  • callocを使った初期化付き確保

動的メモリ管理は最初は少し難しく感じるかもしれませんが、考え方はシンプルです。
必要な分だけ確保し、使い終わったらきちんと解放する。
この基本をしっかり押さえておけば、実用的なC言語プログラムを書くための大きな土台になります。

安全に使うための締めくくりのポイント

mallocとfreeを使うときは、次の流れをいつも意識してみてください。

手順意識したいこと
1ポインタをNULLで初期化する
2mallocで必要サイズを確保する
3NULLチェックを行う
4配列やデータとして使う
5処理が終わったらfreeする
6free後はNULLを代入する

この流れを身につけると、動的メモリに対する苦手意識がかなり減ってきます。
C言語らしいメモリ管理の第一歩として、まずは今回の Lesson59_1 と Lesson59_2 をしっかり動かしながら、確保と解放の流れを自分の手で確認してみてください。