C言語の動的メモリ確保とは?malloc、calloc、realloc、freeの使い方を徹底解説!
静的メモリ管理とは異なり、動的メモリ管理はプログラムの実行状況に合わせて柔軟にメモリを扱うことができます。
動的メモリ管理の利点
- 可変長のデータ構造の処理: 配列や構造体など、要素数が事前に分からないデータ構造を扱うことができます。
- 再利用可能なメモリ: 一度確保したメモリ領域を解放することで、他の処理で再利用することができます。
- 柔軟性の向上: プログラム実行時に必要なメモリ量を動的に確保できるため、必要なメモリ量だけ確保すれば済み、メモリ使用量を削減できます。
動的メモリ管理の仕組み
C言語では、主に malloc()
関数と free()
関数を使用して、動的メモリ管理を行います。
free()
関数:malloc()
で確保したメモリ領域を解放します。malloc()
関数: 指定されたサイズのメモリ領域を確保し、その領域の先頭アドレスをポインタとして返します。メモリ確保に失敗した場合はNULL
を返します。
動的メモリ管理の例
以下は、malloc()
と free()
を使って動的メモリ管理を行う簡単な例です。
#include <stdio.h>
#include <stdlib.h>
int main() {
// 10個の要素を持つ整数の配列を動的に確保
int *array = malloc(sizeof(int) * 10);
// 配列の要素に値を代入
for (int i = 0; i < 10; i++) {
array[i] = i;
}
// 配列の要素を出力
for (int i = 0; i < 10; i++) {
printf("%d ", array[i]);
}
// 使用したメモリ領域を解放
free(array);
return 0;
}
この例では、malloc()
関数を使って 10 個の要素を持つ整数の配列を動的に確保しています。その後、配列の要素に値を代入し、最後に free()
関数を使って確保したメモリ領域を解放しています。
動的メモリ管理を使用する際には、以下の点に注意する必要があります。
- ヌルポインタ参照:
malloc()
関数が失敗した場合、NULL
が返されます。NULL
ポインタをfree()
しようとしたり、NULL
ポインタを介してメモリにアクセスしようとすると、プログラムがクラッシュする可能性があります。 - メモリリーク: メモリ領域を解放し忘れた場合、メモリリークが発生します。メモリリークは、プログラムのパフォーマンス低下やシステム全体のメモリ不足につながる可能性があります。
整数の配列を動的に確保して要素に値を代入する
この例では、malloc()
関数を使って整数の配列を動的に確保し、その要素に値を代入します。その後、free()
関数を使って確保したメモリ領域を解放します。
#include <stdio.h>
#include <stdlib.h>
int main() {
// 10 個の要素を持つ整数の配列を動的に確保
int *array = malloc(sizeof(int) * 10);
// 配列の要素に値を代入
for (int i = 0; i < 10; i++) {
array[i] = i;
}
// 配列の要素を出力
for (int i = 0; i < 10; i++) {
printf("%d ", array[i]);
}
// 使用したメモリ領域を解放
free(array);
return 0;
}
構造体を動的に確保してメンバ変数に値を代入する
この例では、malloc()
関数を使って構造体を動的に確保し、そのメンバ変数に値を代入します。その後、free()
関数を使って確保したメモリ領域を解放します。
#include <stdio.h>
#include <stdlib.h>
typedef struct Person {
char name[32];
int age;
double height;
} Person;
int main() {
// Person 構造体を動的に確保
Person *person = malloc(sizeof(Person));
// 構造体のメンバ変数に値を代入
strcpy(person->name, "Taro Yamada");
person->age = 20;
person->height = 1.75;
// 構造体のメンバ変数の値を出力
printf("名前: %s\n", person->name);
printf("年齢: %d歳\n", person->age);
printf("身長: %.2fcm\n", person->height);
// 使用したメモリ領域を解放
free(person);
return 0;
}
二次元の配列を動的に確保して要素に値を代入する
#include <stdio.h>
#include <stdlib.h>
int main() {
// 3 行 5 列の二次元配列を動的に確保
int **array = malloc(sizeof(int *) * 3);
for (int i = 0; i < 3; i++) {
array[i] = malloc(sizeof(int) * 5);
}
// 二次元配列の要素に値を代入
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
array[i][j] = i * 5 + j;
}
}
// 二次元配列の要素を出力
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
printf("%d ", array[i][j]);
}
printf("\n");
}
// 使用したメモリ領域を解放
for (int i = 0; i < 3; i++) {
free(array[i]);
}
free(array);
return 0;
}
この例では、malloc()
関数を使って可変長の文字列を動的に確保し、その内容を変更します。その後、free()
関数を使って確保したメモリ領域を解放します。
#include <stdio.h>
#include <stdlib.h>
int main() {
// 空の文字列を動的に確保
char *str = malloc(1);
*str = '\0';
// 文字列に文字を追加
str[0] =
calloc() 関数
calloc()
関数は、malloc()
関数と似ていますが、メモリ領域を確保して初期化します。初期化内容は 0 になります。
#include <stdio.h>
#include <stdlib.h>
int main() {
// 10 個の要素を持つ整数の配列を動的に確保して 0 で初期化
int *array = calloc(10, sizeof(int));
// 配列の要素を出力
for (int i = 0; i < 10; i++) {
printf("%d ", array[i]);
}
// 使用したメモリ領域を解放
free(array);
return 0;
}
realloc() 関数
realloc()
関数は、既に確保したメモリ領域のサイズを変更します。メモリ領域を拡張したり、縮小したりすることができます。
#include <stdio.h>
#include <stdlib.h>
int main() {
// 5 個の要素を持つ整数の配列を動的に確保
int *array = malloc(sizeof(int) * 5);
// 配列に値を代入
for (int i = 0; i < 5; i++) {
array[i] = i;
}
// 配列の要素数を 10 に拡張
array = realloc(array, sizeof(int) * 10);
// 追加された要素に値を代入
for (int i = 5; i < 10; i++) {
array[i] = i;
}
// 配列の要素を出力
for (int i = 0; i < 10; i++) {
printf("%d ", array[i]);
}
// 使用したメモリ領域を解放
free(array);
return 0;
}
可変長配列 (VLA)
C99 以降では、可変長配列 (VLA) を使用することができます。VLA は、コンパイル時に配列のサイズを決定できるため、動的メモリ管理よりも効率的にメモリを扱うことができます。
#include <stdio.h>
int main() {
int n;
// ユーザーから配列のサイズを入力
printf("配列のサイズを入力してください: ");
scanf("%d", &n);
// VLA を使用して整数の配列を動的に確保
int array[n];
// 配列の要素に値を代入
for (int i = 0; i < n; i++) {
array[i] = i;
}
// 配列の要素を出力
for (int i = 0; i < n; i++) {
printf("%d ", array[i]);
}
return 0;
}
スマートポインタ
C++11 以降では、スマートポインタを使用して動的メモリ管理をより安全かつ効率的に行うことができます。スマートポインタは、自動的にメモリ領域の解放を行うため、メモリリークを防ぐことができます。
#include <iostream>
#include <memory>
int main() {
// スマートポインタを使用して整数の配列を動的に確保
std::unique_ptr<int[]> array(new int[10]);
// 配列の要素に値を代入
for (int i = 0; i < 10; i++) {
array[i] = i;
}
// 配列の要素を出力
for (int i = 0; i < 10; i++) {
std::cout << array[i] << " ";
}
// スマートポインタが自動的にメモリ領域を解放
return 0;
}
これらの方法は、それぞれ異なる利点と欠点があります。状況に応じて適切な方法を選択する必要があります。
- C言語のメモリリークを防ぐには?原因と対策を徹底解説!