17

コードを非常に防御的に記述し、呼び出すすべての関数からの戻り値を常にチェックするとします。

だから私は次のようになります:

char* function() {
    char* mem = get_memory(100);  // first allocation
    if (!mem) return NULL;

    struct binder* b = get_binder('regular binder');  // second allocation
    if (!b) {
        free(mem);
        return NULL;
    }

    struct file* f = mk_file();  // third allocation
    if (!f) {
        free(mem);
        free_binder(b);
        return NULL;
    }

    // ...
}

free()物事がいかに早く制御不能になるかに注目してください。一部の関数が失敗した場合は、前にすべての割り当てを解放する必要があります。コードはすぐに醜くなり、私がすることはすべてをコピーして貼り付けることだけです。私はコピー/貼り付けプログラマーになります。さらに悪いことに、誰かがその間にステートメントを追加した場合、彼はfree()追加を呼び出すために以下のすべてのコードを変更する必要があります。

経験豊富なCプログラマーはこの問題にどのように取り組んでいますか?何もわからない。

ありがとう、BodaCydo。

4

6 に答える 6

37

リソースを解放する新しいラベルを定義して、コードが失敗するたびにそれをGOTOすることができます。

char* function()
{
    char* retval = NULL;
    char* mem = get_memory(100);  // first allocation
    if (!mem)
        goto out_error;

    struct binder* b = get_binder('regular binder');  // second allocation
    if (!b)
        goto out_error_mem;

    struct file* f = mk_file();  // third allocation
    if (!f)
        goto out_error_b;

    /* ... Normal code path ... */
    retval = good_value;

  out_error_b:
    free_binder(b);
  out_error_mem:
    free(mem);
  out_error:
    return retval;
}

GOTOを使用したエラー管理については、ここですでに説明し ました。Cでのエラー管理にgotoを有効に使用しますか?

于 2010-07-27T00:34:15.810 に答える
6

私はこれのためにリンチされることを知っています、しかし私には彼らがそれのために使用gotoしたと言った友人がいました。

それから彼はそれがほとんどの場合十分ではなかったと私に言った、そして彼は今setjmp()/を使っlongjmp()た。基本的に、彼はC ++の例外を再発明しましたが、エレガンスははるかに劣っています。

とはいえ、機能する可能性があるため、使用しないものにリファクタリングすることはできますgoto goto、インデントはすぐに手に負えなくなります。

char* function() {
    char* result = NULL;
    char* mem = get_memory(100);
    if(mem) {
        struct binder* b = get_binder('regular binder');
        if(b) {
            struct file* f = mk_file();
            if (f) {
                // ...
            }
            free(b);
        }
        free(mem);
    }
    return result;
}

ところで、そのようなローカル変数宣言をブロックの周りに分散させることは、標準のCではありません。

これでfree(NULL);、C標準で何もしないように定義されていることに気付いた場合は、ネストを簡略化できます。

char* function() {
    char* result = NULL;

    char* mem = get_memory(100);
    struct binder* b = get_binder('regular binder');
    struct file* f = mk_file();

    if (mem && b && f) {
        // ...
    }

    free(f);
    free(b);
    free(mem);

    return result;
}
于 2010-07-27T00:42:42.790 に答える
5

防御的なコーディングに対するあなたのアプローチには感心しますが、それは良いことです。そして、すべてのCプログラマーはその考え方を持っている必要があり、他の言語にも適用できます...

純粋主義者は別の言い方をしますが、これはGOTOの便利な点のひとつだと言わざるを得ません。これは、finallyブロックに相当しますが、そこに見られる特定の落とし穴が1つあります...

karlphillipのコードはほぼ完成していますが、....関数がこのように実行されたと仮定します

    char* function() {
      struct file* f = mk_file();  // third allocation
      if (!f) goto release_resources;
      // DO WHATEVER YOU HAVE TO DO.... 
      return some_ptr;
   release_resources:
      free(mem);
      free_binder(b);
      return NULL;
    }

気をつけて!!!release_resourcesこれは、あなたが適切だと思う機能のデザインと目的に依存しますが、それは脇に置いておきます。そのような機能から戻ると、ラベルを通り抜けてしまう可能性があります。バグ、ヒープ上のポインタへのすべての参照が失われ、ガベージが返される可能性があります...メモリが割り当てられている場合はreturn、ラベルの直前にキーワードを使用してください。そうしないと、メモリが失われる可能性があります。 。またはメモリリークを作成します...

于 2010-07-27T00:53:43.520 に答える
3

反対のアプローチを取り、成功を確認することもできます。

struct binder* b = get_binder('regular binder');  // second allocation
if(b) {
   struct ... *c = ...
   if(c) {
      ...
   }
   free(b);
}
于 2010-07-27T08:31:28.443 に答える
2

なしでそれを実行したい場合gotoは、次のアプローチが適切に拡張されます。

char *function(char *param)
{
    int status = 0;   // valid is 0, invalid is 1
    char *result = NULL;
    char *mem = NULL:
    struct binder* b = NULL;
    struct file* f = NULL:

    // check function parameter(s) for validity
    if (param == NULL)
    {
        status = 1;
    }

    if (status == 0)
    {
        mem = get_memory(100);  // first allocation

        if (!mem)
        {
            status = 1;
        }
    }

    if (status == 0)
    {
        b = get_binder('regular binder');  // second allocation

        if (!b)
        {
             status = 1;
        }
    }

    if (status == 0)
    {
        f = mk_file();  // third allocation

        if (!f)
        {
             status = 1;
        }
    }

    if (status == 0)
    {
        // do some useful work
        // assign value to result
    }

    // cleanup in reverse order
    free(f);
    free(b);
    free(mem);

    return result;
}
于 2010-07-27T04:18:01.873 に答える
2

データ構造が複雑/ネストされている場合は、1つのgotoでは不十分な場合があります。その場合は、次のようなものをお勧めします。

mystruct = malloc(sizeof *mystruct);
if (!mystruct) goto fail1;
mystruct->data = malloc(100);
if (!mystruct->data) goto fail2;
foo = malloc(sizeof *foo);
if (!foo) goto fail2;
...
return mystruct;
fail2:
free(mystruct->data);
fail1:
free(mystruct);

実際の例はもっと複雑で、複数レベルの構造体のネスト、リンクリストなどが含まれる可能性があります。最初に失敗した場合は(の要素の逆参照が無効free(mystruct->data);であるため)呼び出すことができないことに注意してください。mystructmalloc

于 2010-07-27T05:54:01.153 に答える