web-dev-qa-db-ja.com

特に複数のmallocがある場合、Cでmallocの失敗を正しく処理するにはどうすればよいですか?

これが私のコードの一部であるとしましょう:

_ 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を使用している可能性がありますか?)

32
Roun

あなたのコードは問題ありませんが、多くの変数に対して、私は好むでしょう:

_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行のメッセージは、エラーが検出された関数の名前とエラーの簡単な説明のみを含むように簡略化されます。

34
abligh

NULLfree()に渡すことは完全に問題ないので、必要なすべてのものを「直線」で割り当て、すべてを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であると想定せずに、そのような構造を解放する関数を定義することをお勧めします。

25
dasblinkenlight

多数の割り当ての場合、割り当てを追跡するメモリマネージャの作成に時間を費やします。そうすれば、関数が成功したかどうかにかかわらず、リークを心配する必要はありません。

一般的なアイデアは、成功した割り当てを記録し、要求に応じてそれらを解放する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;
}
3
user3386109

大量のアイテムを割り当てることを期待している場合、それは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をラップできます。配列と配列サイズを構造体に入れ、必要な割り当てサイズで渡します。

2
kdopen

それは習慣の問題ですが、私は好みます:

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
2
Iłya Bursov

最初の答えは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;
}
2
Gray