これが私のコードの一部であるとしましょう:
_ int foo()
{
char *p, *q ;
if((p = malloc(BUFSIZ)) == NULL) {
return ERROR_CODE;
}
if((q = malloc(BUFSIZ)) == NULL) {
free(p)
return ERROR_CODE;
}
/* Do some other work... */
free(p);
free(q);
}
_
最初のmalloc
は成功する可能性がありますが、2番目は失敗する可能性があるため、2番目の「エラーハンドラ」でfree(p)
を使用します。しかし、malloc
がもっとある場合や、コードを変更したい場合はどうすればよいですか(順序を調整し、malloc
を追加または削除します)。
C++にはRAIIや例外セーフなどの機能があることはわかっていますが、一般的に、Cでmalloc
の失敗を処理する正しい方法は何ですか? (goto
を使用している可能性がありますか?)
あなたのコードは問題ありませんが、多くの変数に対して、私は好むでしょう:
_int
foo()
{
char *p = NULL;
char *q = NULL;
int ret = 0;
if (NULL == (p = malloc(BUFSIZ)))
{
ret = ERROR_CODE;
goto error;
}
// possibly do something here
if (NULL == (q = malloc(BUFSIZ)))
{
ret = ERROR_CODE;
goto error;
}
// insert similar repetitions
// hopefully do something here
error:
free (p);
free (q);
return ret;
}
_
NULL
の解放は何もしないとして定義されていることに注意してください。
これにより、n
変数のn
レベルのインデントが回避されます。同様にファイルハンドルなどをクリーンアップできます(ただし、close()
の周りに条件を設定する必要があります)。
これらを一度に割り当てることができることがわかっている場合は、dasblinkenlightが適切な答えを示しますが、別の方法があります。
_int
foo()
{
int ret = 0;
char *p = malloc(BUFSIZ);
char *q = malloc(BUFSIZ);
char *r = malloc(BUFSIZ);
if (!p || !q || !r)
{
ret = ERROR_CODE;
goto exit;
}
// do something
exit:
free(p);
free(q);
free(r);
return ret;
}
_
最終的な可能性:malloc
が失敗したときにプログラムを実際に終了したい場合は、mallopt
の_M_CHECK_ACTION
_オプションの使用を検討してください。これにより、malloc()
の障害がチェックされ、abort()
が呼び出され、役立つメッセージが出力される場合があります。
Manページから:
[〜#〜]名前[〜#〜]
mallopt
-メモリ割り当てパラメータを設定します[〜#〜] synopsis [〜#〜]
_#include <malloc.h> int mallopt(int param, int value);
_[〜#〜]説明[〜#〜]
mallopt()
関数は、メモリ割り当て関数の動作を制御するパラメータを調整します(malloc(3)
を参照)。param
引数は変更するパラメーターを指定し、value
はそのパラメーターの新しい値を指定します。
param
には次の値を指定できます。_
M_CHECK_ACTION
_このパラメーターを設定すると、さまざまな種類のプログラミングエラーが検出されたとき(たとえば、同じポインターを2回解放するなど)のglibcの応答を制御します。このパラメーターに割り当てられた値の最下位3ビット(2、1、および0)は、次のようにglibcの動作を決定します。
ビット:このビットが設定されている場合、
stderr
にエラーに関する詳細を提供する1行のメッセージを出力します。メッセージは文字列_"*** glibc detected ***"
_で始まり、その後にプログラム名、エラーが検出されたメモリ割り当て関数の名前、エラーの簡単な説明、エラーが検出されたメモリアドレスが続きます。ビット1:このビットが設定されている場合、ビット0で指定されたエラーメッセージを出力した後、
abort(3)
を呼び出してプログラムを終了します。 2.4以降のglibcバージョンでは、ビット0も設定されている場合、エラーメッセージの出力と中止の間に、プログラムはbacktrace(3)
の方法でスタックトレースも出力し、プロセスのメモリマッピングを_/proc/[pid]/maps
_のスタイル(proc(5)
を参照)。ビット2:(glibc 2.4以降)このビットは、ビット0も設定されている場合にのみ効果があります。このビットが設定されている場合、エラーを説明する1行のメッセージは、エラーが検出された関数の名前とエラーの簡単な説明のみを含むように簡略化されます。
NULL
をfree()
に渡すことは完全に問題ないので、必要なすべてのものを「直線」で割り当て、すべてを1回のショットでチェックし、すべてが解放されたら実際に何らかの作業を行ったかどうかに関係なく、完了:
char *p = malloc(BUFSIZ);
char *q = malloc(BUFSIZ);
char *r = malloc(BUFSIZ);
if (p && q && r) {
/* Do some other work... */
}
free(p);
free(q);
free(r);
これは、中間の依存関係がない限り機能します。つまり、マルチレベルの依存関係を持つ構造体がない場合です。その場合、すべてのメモリブロックが非NULL
であると想定せずに、そのような構造を解放する関数を定義することをお勧めします。
多数の割り当ての場合、割り当てを追跡するメモリマネージャの作成に時間を費やします。そうすれば、関数が成功したかどうかにかかわらず、リークを心配する必要はありません。
一般的なアイデアは、成功した割り当てを記録し、要求に応じてそれらを解放するmalloc
のラッパーを作成することです。メモリを解放するには、ラッパー関数に特別なサイズを渡すだけです。実際の割り当てが0
サイズのブロックに当てはまらないことがわかっている場合は、0
のサイズを使用してメモリを解放するのが適切です。それ以外の場合は、解放要求サイズとして~0ULL
を使用できます。
以下は、無料の間に最大100の割り当てを許可する簡単な例です。
#define FREE_ALL_MEM 0
void *getmem( size_t size )
{
static void *blocks[100];
static int count = 0;
// special size is a request to free all memory blocks
if ( size == FREE_ALL_MEM )
{
for ( int i = 0; i < count; i++ )
free( blocks[i] );
count = 0;
return NULL;
}
// using a linked list of blocks would allow an unlimited number of blocks
// or we could use an array that can be expanded with 'realloc'
// but for this example, we have a fixed size array
if ( count == 100 )
return NULL;
// allocate some memory, and save the pointer in the array
void *result = malloc( size );
if ( result )
blocks[count++] = result;
return result;
}
int foo( void )
{
char *p, *q;
if ( (p = getmem(BUFSIZ)) == NULL ) {
return ERROR_CODE;
}
if ( (q = getmem(BUFSIZ)) == NULL ) {
getmem( FREE_ALL_MEM );
return ERROR_CODE;
}
/* Do some other work... */
getmem( FREE_ALL_MEM );
return SUCCESS_CODE;
}
大量のアイテムを割り当てることを期待している場合、それはCan乱雑になります。 'goto'アプローチは避けてください。古い「後藤は悪い」倫理のためではなく、その方法は本当に狂気とメモリリークを引き起こす可能性があるからです。
少数のmallocには少しやり過ぎですが、このアプローチのようなものを検討できます。
void free_mem(void **ptrs, size_t len)
{
for (size_t i = 0; i < len; ++i)
{
free(ptrs[i]);
ptrs[i] = NULL;
}
}
int foo(...)
{
void *to_be_freed[N];
int next_ptr = 0;
for (size_t i = 0; i < N; ++i) to_be_freed[i] = NULL;
p = malloc(..);
if (!p)
{
free_mem(to_be_freed,N);
return ERROR_CODE;
}
to_be_freed[next_ptr++] = p;
// Wash, rinse, repeat, with other mallocs
free_mem(to_be_freed,N)
return SUCCESS;
}
実際には、おそらくこれを追跡するものでmallocをラップできます。配列と配列サイズを構造体に入れ、必要な割り当てサイズで渡します。
それは習慣の問題ですが、私は好みます:
int returnFlag = FAILURE;
if ((p = malloc...) != NULL)
{
if ((q = malloc..) != NULL)
{
// do some work
returnFlag = SUCCESS; // success only if it is actually success
free(q);
}
free(p);
}
return returnFlag; // all other variants are failure
最初の答えはmalloc以外のエラーに使用できるため、最も一般的な目的だと思います。しかし、私は後藤を削除し、ループのような単一のパスをループのように使用します。
int foo()
{
char *p = NULL;
char *q = NULL;
int ret = 0;
do {
if (NULL == (p = malloc(BUFSIZ)))
{
ret = ERROR_CODE;
break;
}
// possibly do something here
if (NULL == (q = malloc(BUFSIZ)))
{
ret = ERROR_CODE;
break;
}
// insert similar repetitions
// hopefully do something here
} while(0);
free (p);
free (q);
return ret;
}