開発を行っていると、関数内で複数の動的メモリを確保しなければならないケースに遭遇します。
処理の途中でメモリ確保に失敗した場合、それまでに確保したメモリを解放しなければいけないため、どうしてもエラー処理が複雑になってしまいます。
ここでは、goto
文を使用して、メモリ解放をシンプルにする方法について解説します。
goto文とは
goto
文は、関数内で指定したラベルへのジャンプを行うための構文です。以下のような形式を取ります。
goto <ラベル名>;
ラベル名は、関数内の任意の位置に指定することができる識別子です。goto
文はこのラベル名に対応する箇所へジャンプします。
goto
文は、プログラムの可読性や保守性を低下させる可能性があるため、使用を避ける傾向があります。
しかし、うまく活用することで、プログラムはよりシンプルになり、同時に可読性や保守性を向上させることができます。
メモリ解放を伴う複雑なエラー処理
複数の動的メモリを確保する処理では、メモリ確保に失敗した場合、それまでに確保したメモリを解放しなければいけないため、どうしてもエラー処理が複雑になってしまいます。
以下は、複雑なメモリ管理を行うコードの例です。
int example(void)
{
int *p1, *p2, *p3;
p1 = (int *)malloc(sizeof(int) * 10); /* p1のメモリ確保 */
if (p1 == NULL)
{
return -1;
}
p2 = (int *)malloc(sizeof(int) * 10); /* p2のメモリ確保 */
if (p2 == NULL)
{
free(p1); /* p1のメモリ解放 */
return -1;
}
p3 = (int *)malloc(sizeof(int) * 10); /* p3のメモリ確保 */
if (p3 == NULL)
{
free(p1); /* p1のメモリ解放 */
free(p2); /* p2のメモリ解放 */
return -1;
}
/* 処理 */
free(p1); /* p1のメモリ解放 */
free(p2); /* p2のメモリ解放 */
free(p3); /* p3のメモリ解放 */
return 0;
}
この例では、複数の動的メモリを順番に確保し、処理を行った後、最後に確保したメモリの解放を行っています。
また、メモリ確保に失敗した場合は、それまでに確保したメモリの解放を行っています。
このコードでも問題なく動作しますが、メモリ確保と解放が1対1で対応しておらず、メモリリークの検出が困難なため、可読性が良いとは言えません。
小規模なコードの場合、注意深く確認することでメモリリークの検出が可能ですが、大規模なコードの場合は、メモリリークの検出が困難になってしまいます。
goto文でメモリ解放をシンプルにする
goto
文を利用すると、メモリ解放を1か所で行うことができるため、プログラムの可読性や保守性を向上させることができます。
以下は、先ほどの例をgoto
文でシンプルにしたコードです。
int example(void)
{
int ret = -1;
int *p1 = NULL, *p2 = NULL, *p3 = NULL;
p1 = (int *)malloc(sizeof(int) * 10); /* p1のメモリ確保 */
if (p1 == NULL)
{
goto cleanup;
}
p2 = (int *)malloc(sizeof(int) * 10); /* p2のメモリ確保 */
if (p2 == NULL)
{
goto cleanup;
}
p3 = (int *)malloc(sizeof(int) * 10); /* p3のメモリ確保 */
if (p3 == NULL)
{
goto cleanup;
}
/* 処理 */
ret = 0;
cleanup:
free(p1); /* p1のメモリ解放 */
free(p2); /* p2のメモリ解放 */
free(p3); /* p3のメモリ解放 */
return ret;
}
先ほどの例と同様に、複数の動的メモリを順番に確保し、処理を行った後、最後に確保したメモリの解放を行っています。
ただし、メモリ確保に失敗した場合、goto
文を使用してメモリ解放処理までジャンプすることで、正常終了時のメモリ解放と処理を共通化させています。
これにより、メモリ確保と解放を1対1の関係にできるため、メモリリークの検出が目視でも簡単に行えるようになりました。
free()
関数の引数がNULL
の場合、何も処理を行わないことが言語仕様で定められています。
おわりに
goto
文は、誤った使い方をした場合、可読性や保守性を低下させる可能性がありますが、適切に使用することで、処理をとてもシンプルにすることができます。
今回は、メモリ管理におけるエラー処理を例に挙げましたが、そのほかにも有効な活用方法が多数存在します。
状況に応じて使い分けることが重要です。
コメント