Skip to main content

KTDEVX

【C言語】動的メモリ確保の方法と注意点

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; /* 成功した場合は上書き */
}

こうすることで、メモリの再確保に失敗した場合でも、元となるポインタ変数ptrNULLで上書きされることはありません。

## 不要となったメモリは解放する

malloc関数で割り当てたメモリ領域は、必ずfree関数で解放する必要があります。解放しないと、メモリリークが発生し、システム全体のメモリを使い尽くしてしまう可能性があります。

メモリ確保と解放が関数内で1対1となるようにプログラムすることで、目視での確認が容易となり、メモリリークの発生を抑えることができます。

## メモリの二重解放をしない

動的に確保したメモリを1度解放した後、再度同じ領域を解放すると未定義の動作となります。

解放されたメモリを再利用している他の処理の動作が不安定になったり、プログラムがクラッシュする可能性があるため、メモリの二重解放に注意する必要があります。

free関数では引数にNULLが指定された場合、メモリ解放が行われないことが仕様として定義されています。そのため、メモリを解放した後のポインタ変数にはNULLを代入することを推奨します。

free(ptr);
ptr = NULL;