malloc() / calloc() / realloc() / free()
| 対応: | C89(1989) |
|---|
プログラムの実行中に必要な分だけメモリを確保し、使い終わったら解放する仕組みです。配列のサイズを実行時に決めたいときや、大きなデータを扱うときに使用します。これらの関数は『<stdlib.h>』に含まれています。
構文
#include <stdlib.h> // size バイトのメモリを確保(初期化なし) void *malloc(size_t size); // num × size バイトを確保し 0 で初期化 void *calloc(size_t num, size_t size); // 確保済みメモリを new_size バイトに変更 void *realloc(void *ptr, size_t new_size); // 確保したメモリを解放 void free(void *ptr);
動的メモリ関数一覧
| 関数 | 概要 |
|---|---|
| malloc(size) | 指定バイト数のメモリをヒープから確保します。初期値は不定です。確保に失敗するとNULLを返します。 |
| calloc(num, size) | num個のsize バイト要素を確保し、全バイトをゼロで初期化します。整数配列の初期化に便利です。 |
| realloc(ptr, new_size) | ptrが指すメモリ領域のサイズをnew_sizeに変更します。必要なら新しい場所に移動し、元の内容をコピーします。 |
| free(ptr) | malloc/calloc/reallocで確保したメモリを解放します。NULLを渡しても安全です。解放済みのポインタを再解放してはいけません。 |
サンプルコード
malloc で実行時にサイズを決めて配列を確保し、calloc でゼロ初期化した配列を作成し、realloc で既存の配列を拡張します。
sample_malloc_free.c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n;
printf("要素数を入力してください: ");
scanf("%d", &n);
// malloc: 実行時にサイズを決めて配列確保
int *arr = malloc(sizeof(int) * n);
if (arr == NULL) {
fprintf(stderr, "メモリの確保に失敗しました。\n");
return 1;
}
for (int i = 0; i < n; i++) {
arr[i] = (i + 1) * 10;
}
for (int i = 0; i < n; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
free(arr);
arr = NULL; // ダングリングポインタ防止
// calloc: 全要素 0 で初期化される
int *zeros = calloc(5, sizeof(int));
for (int i = 0; i < 5; i++) {
printf("%d ", zeros[i]);
}
printf("\n");
free(zeros);
// realloc: 既存の配列を動的に拡張
int *buf = malloc(sizeof(int) * 3);
buf[0] = 1; buf[1] = 2; buf[2] = 3;
int *new_buf = realloc(buf, sizeof(int) * 6);
if (new_buf == NULL) {
free(buf);
return 1;
}
buf = new_buf;
buf[3] = 4; buf[4] = 5; buf[5] = 6;
for (int i = 0; i < 6; i++) {
printf("%d ", buf[i]);
}
printf("\n");
free(buf);
return 0;
}
gcc sample_malloc_free.c -o sample_malloc_free ./sample_malloc_free 要素数を入力してください: 3 arr[0] = 10 arr[1] = 20 arr[2] = 30 0 0 0 0 0 1 2 3 4 5 6
malloc / calloc / realloc の比較
| 関数 | 初期化 | 用途 | 適した場面 |
|---|---|---|---|
| malloc(size) | しない(値は不定) | 汎用メモリ確保。速い | 速度重視・後で書き込む場合 |
| calloc(num, size) | 全バイトを0に初期化 | 整数配列などゼロ初期化が必要な場合 | 初期値0が必要な場合 |
| realloc(ptr, size) | 拡張部分は不定 | 確保済み領域のサイズ変更 | 動的に伸縮する配列 |
整数・フラグ配列をゼロ初期化して使いたい場合は『calloc()』を使うとゼロ初期化されます。mallocより明示的で、memset(0)を書く手間が省けます。
メモリリーク防止策
確保したメモリを解放しないと「メモリリーク」が発生し、長時間動作するプログラムでメモリ使用量が増え続けます。
realloc の戻り値を元のポインタに直接代入すると、失敗時に元のメモリが失われます。
realloc_ng.c
#include <stdio.h>
#include <stdlib.h>
int *bad_grow(int *buf, int new_size) {
buf = realloc(buf, sizeof(int) * new_size);
return buf;
}
int main(void) {
int *buf = malloc(sizeof(int) * 3);
buf[0] = 10; buf[1] = 20; buf[2] = 30;
buf = bad_grow(buf, 6);
buf[3] = 40; buf[4] = 50; buf[5] = 60;
for (int i = 0; i < 6; i++) {
printf("%d ", buf[i]);
}
printf("\n");
free(buf);
return 0;
}
通常は動作しますが、realloc が失敗すると元のメモリが失われます。
gcc realloc_ng.c -o realloc_ng ./realloc_ng 10 20 30 40 50 60
別の変数で NULL チェックしてから代入することで、失敗時も安全にメモリを回収できます。
realloc_ok.c
#include <stdio.h>
#include <stdlib.h>
int *good_grow(int *buf, int new_size) {
int *tmp = realloc(buf, sizeof(int) * new_size);
if (tmp == NULL) {
free(buf);
return NULL;
}
return tmp;
}
int main(void) {
int *buf = malloc(sizeof(int) * 3);
buf[0] = 10; buf[1] = 20; buf[2] = 30;
buf = good_grow(buf, 6);
if (buf == NULL) {
fprintf(stderr, "メモリの確保に失敗しました。\n");
return 1;
}
buf[3] = 40; buf[4] = 50; buf[5] = 60;
for (int i = 0; i < 6; i++) {
printf("%d ", buf[i]);
}
printf("\n");
free(buf);
return 0;
}
gcc realloc_ok.c -o realloc_ok ./realloc_ok 10 20 30 40 50 60
早期 return がある関数でも、すべてのパスで free を呼んでください。
early_return.c
#include <stdio.h>
#include <stdlib.h>
int process(int trigger_error) {
int *data = malloc(sizeof(int) * 100);
if (data == NULL) { return -1; }
if (trigger_error) {
free(data);
return -1;
}
data[0] = 42;
printf("data[0] = %d\n", data[0]);
free(data);
data = NULL;
return 0;
}
int main(void) {
printf("正常系: %d\n", process(0));
printf("異常系: %d\n", process(1));
return 0;
}
gcc early_return.c -o early_return ./early_return data[0] = 42 正常系: 0 異常系: -1
よくあるミス1: NULLチェックの省略
malloc 後に NULL チェックしないと、メモリ不足時にクラッシュします。
null_check_ng.c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *arr = malloc(sizeof(int) * 1000000);
arr[0] = 1;
printf("%d\n", arr[0]);
free(arr);
return 0;
}
malloc が NULL を返した場合(メモリ不足時)
./null_check_ng Segmentation fault (core dumped)
null_check_ok.c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *arr = malloc(sizeof(int) * 1000000);
if (arr == NULL) {
fprintf(stderr, "メモリの確保に失敗しました。\n");
return 1;
}
arr[0] = 1;
printf("%d\n", arr[0]);
free(arr);
return 0;
}
gcc null_check_ok.c -o null_check_ok ./null_check_ok 1
よくあるミス2: 二重解放
同じポインタを 2 回 free すると未定義動作になります。解放後に NULL を代入することで防止できます。
double_free_ng.c
#include <stdlib.h>
int main(void) {
int *arr = malloc(sizeof(int) * 10);
free(arr);
free(arr);
return 0;
}
gcc double_free_ng.c -o double_free_ng ./double_free_ng free(): double free detected in tcache 2 Aborted (core dumped)
double_free_ok.c
#include <stdlib.h>
int main(void) {
int *arr = malloc(sizeof(int) * 10);
free(arr);
arr = NULL;
free(arr);
return 0;
}
gcc double_free_ok.c -o double_free_ok ./double_free_ok
(正常終了・出力なし)
よくあるミス3: reallocの直接代入
realloc の戻り値を元のポインタに直接代入すると、失敗時に元のメモリが失われます。別の変数で受け取ってください。
arr = realloc(arr, new_size);
int *tmp = realloc(arr, new_size);
if (tmp == NULL) { free(arr); return 1; }
arr = tmp;
概要
動的に確保したメモリは「ヒープ」と呼ばれる領域に置かれます。スタック上の自動変数とは異なり、明示的に『free()』を呼ぶまで解放されません。確保したメモリを解放しない「メモリリーク」はプログラムの実行時間とともにメモリ使用量が増え続ける原因になります。必ず確保したメモリは解放してください。
また、解放済みのメモリを再度使用する「ダングリングポインタ」や、同じメモリを二重に解放する「二重解放」は深刻なバグを引き起こします。解放後は必ずポインタに『NULL』を代入してください。
『realloc()』は失敗したとき元のポインタを解放しないため、必ず別の変数でNULLチェックしてから代入してください。メモリに関連する関数の詳細は『sizeof』も合わせて参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。