【C言語】動的メモリ確保の方法と注意点
- EN
- JA
Table of Contents
C言語では、メモリの動的確保を行うことができます。動的確保は、必要なメモリ領域が実行時に変化する場合や、大量のメモリ領域が必要な場合に便利です。
ここでは、メモリを動的に確保する方法と、その注意点について解説します。
#
動的メモリ確保と解放を行う関数
C言語では、動的なメモリの確保と解放を行うための関数が、標準ライブラリで提供されています。
#include <stdlib.h>
void *malloc(size_t size);
void free(void *ptr);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
使用するには、stdlib.h
をインクルードします。
#
メモリの動的確保
メモリを動的に確保するにはmalloc
関数を使用します。
void *malloc(size_t size);
malloc
関数は、引数に必要なメモリ領域のサイズをバイト単位で指定します。メモリ割り当てに成功した場合、割り当てられたメモリ領域の先頭アドレスが戻り値で返されます。失敗した場合はNULL
が返されます。
例えば、10個の要素を持つint
型の配列を割り当てる場合は、以下のように書きます。
int *ptr;
ptr = (int *)malloc(sizeof(int) * 10);
この例では、int
型のサイズをsizeof
演算子で取得し、10倍した値をmalloc
関数に渡しています。これにより、int
型のサイズ×10のメモリ領域が割り当てられ、その先頭アドレスがポインタ変数ptr
に代入されます。
割り当てられたメモリ領域は、ポインタ変数を介して通常の配列と同じように操作することができます。
#
確保したメモリの解放
動的に確保したメモリ領域は、使用後に必ず解放する必要があります。解放しないとメモリリークが発生し、システム全体のメモリを使い尽くしてしまう可能性があります。解放するためにはfree
関数を使用します。
void free(void *ptr);
free
関数は、引数に解放するメモリ領域の先頭アドレスを指定します。
free(ptr);
これにより、動的に確保されたメモリ領域が解放されます。
#
初期化されたメモリの確保
calloc
関数を使用することで、値が0で初期化されたメモリを確保することができます。
void *calloc(size_t nmemb, size_t size);
malloc
と引数は異なりますが、基本的な考え方は同じです。
引数size
に要素のサイズ、nmemb
に要素の数を指定してメモリの確保を行います。メモリ割り当てに成功した場合、割り当てられたメモリ領域の先頭アドレスが戻り値で返されます。失敗した場合はNULL
が返されます。
int *ptr;
ptr = (int *)calloc(10, sizeof(int));
この例では、要素のサイズにint
型のサイズを指定し、要素の数を10としています。これにより、int
型のサイズ×10のメモリ領域がポインタ変数ptr
に割り当てられます。
また、calloc
関数で割り当てられたメモリの値は、全て0で初期化されています。
#
メモリの再確保
realloc
関数を使用することで、malloc
関数やcalloc
関数で確保したメモリのサイズを変更することができます。
void *realloc(void *ptr, size_t size);
realloc
関数は、引数ptr
が示すメモリのサイズを、引数size
で指定されたサイズに変更します。メモリ割り当てに成功した場合、割り当てられたメモリ領域の先頭アドレスが戻り値で返されます。失敗した場合はNULL
が返されます。
int *new_ptr;
new_ptr = (int *)realloc(ptr, sizeof(int) * 20);
この例では、malloc
関数やcalloc
関数で確保したメモリの先頭アドレスを渡し、変更後のサイズとしてint
型のサイズ×20を指定しています。これにより、サイズ変更されたメモリ領域がnew_ptrに割り当てられます。
#
動的メモリ確保の注意点
動的メモリ確保には、以下のような注意点があります。
##
メモリ確保の結果を確認する
動的メモリ確保は失敗する可能性があります。そのため、戻り値を必ずチェックするようにしましょう。
メモリ確保に失敗した場合、関数は戻り値としてNULL
を返します。NULL
ポインタの参照は未定義の動作を引き起こすため、必ず関数の戻り値を確認する必要があります。
int *ptr;
ptr = (int *)malloc(sizeof(int) * 10);
if (ptr == NULL)
{
/* エラー処理 */
}
この例では、malloc
関数を例として説明していますが、calloc
関数やrealloc
関数でも同様に戻り値の確認が必要です。
##
realloc関数の戻り値は引数とは異なるポインタ変数で受け取る
メモリを確保する関数ではメモリが確保できなかった場合、NULL
を返すようになっています。
realloc
関数の戻り値を引数と同じポインタ変数で受けると、メモリが確保できなかった場合、元となったポインタ変数がNULL
で上書きされることになってしまいます。
以下のように一時的なポインタ変数を準備し、戻り値がNULL
ではない場合に上書きする必要があります。
int *tmp;
tmp = (int *)realloc(ptr, sizeof(int) * 20);
if (tmp == NULL)
{
/* エラー処理 */
}
else
{
ptr = tmp; /* 成功した場合は上書き */
}
こうすることで、メモリの再確保に失敗した場合でも、元となるポインタ変数ptr
がNULL
で上書きされることはありません。
##
不要となったメモリは解放する
malloc
関数で割り当てたメモリ領域は、必ずfree
関数で解放する必要があります。解放しないと、メモリリークが発生し、システム全体のメモリを使い尽くしてしまう可能性があります。
メモリ確保と解放が関数内で1対1となるようにプログラムすることで、目視での確認が容易となり、メモリリークの発生を抑えることができます。
##
メモリの二重解放をしない
動的に確保したメモリを1度解放した後、再度同じ領域を解放すると未定義の動作となります。
解放されたメモリを再利用している他の処理の動作が不安定になったり、プログラムがクラッシュする可能性があるため、メモリの二重解放に注意する必要があります。
free
関数では引数にNULL
が指定された場合、メモリ解放が行われないことが仕様として定義されています。そのため、メモリを解放した後のポインタ変数にはNULL
を代入することを推奨します。
free(ptr);
ptr = NULL;