6

はい、2つの嫌いな構造が組み合わされています. それとも、goto の使用を制御し、適切なクリーンアップ戦略を提供するための良い方法と見なすことができますか?

職場では、コーディング標準で goto を許可するかどうかについて話し合いました。一般に、誰も goto を自由に使用できるようにしたくはありませんでしたが、クリーンアップ ジャンプに使用することに前向きな人もいました。このコードのように:

void func()
{
   char* p1 = malloc(16);
   if( !p1 )
      goto cleanup;

   char* p2 = malloc(16);
   if( !p2 )
      goto cleanup;

 goto norm_cleanup;

 err_cleanup:

   if( p1 )
      free(p1);

   if( p2 )
      free(p2);

 norm_cleanup:
}

このような使用の上記の利点は、次のコードで終わる必要がないことです。

void func()
{
   char* p1 = malloc(16);
   if( !p1 ){
      return;
   }

   char* p2 = malloc(16);
   if( !p2 ){
      free(p1);
      return;
   }

   char* p3 = malloc(16);
   if( !p3 ){
      free(p1);
      free(p2);
      return;
   }
}

特に、多くの割り当てがあるコンストラクターのような関数では、特に誰かが途中で何かを挿入しなければならない場合に、これは非常に悪くなることがあります。

そのため、goto を使用できるようにしつつ、自由に使用されないように明確に分離するために、一連のフロー制御マクロがタスクを処理するために作成されました。次のようになります (簡略化)。

#define FAIL_SECTION_BEGIN int exit_code[GUID] = 0;
#define FAIL_SECTION_DO_EXIT_IF( cond, exitcode ) if(cond){exit_code[GUID] = exitcode; goto exit_label[GUID];}
#define FAIL_SECTION_ERROR_EXIT(code) exit_label[GUID]: if(exit_code[GUID]) int code = exit_code[GUID];else goto end_label[GUID]
#define FAIL_SECTION_END end_label[GUID]:

これは次のように使用できます。

int func()
{
   char* p1 = NULL;
   char* p2 = NULL;
   char* p3 = NULL;

   FAIL_SECTION_BEGIN
   {
      p1 = malloc(16);
      FAIL_SECTION_DO_EXIT_IF( !p1, -1 );

      p2 = malloc(16);
      FAIL_SECTION_DO_EXIT_IF( !p2, -1 );

      p3 = malloc(16);
      FAIL_SECTION_DO_EXIT_IF( !p3, -1 );
   }
   FAIL_SECTION_ERROR_EXIT( code )
   {
      if( p3 ) 
         free(p3);

      if( p2 ) 
         free(p2);

      if( p1 ) 
         free(p1);

      return code;
    }
    FAIL_SECTION_END

  return 0;

見栄えがよく、多くの利点がありますが、これを開発に展開する前に考慮すべき欠点はありますか? 結局のところ、非常にフロー制御と goto:ish です。どちらも落胆しています。この場合、彼らを思いとどまらせるための議論は何ですか?

ありがとう。

4

9 に答える 9

11

エラー処理は、gotoそれほど悪くないまれな状況の 1 つです。

しかし、そのコードを維持しなければならない場合goto、マクロによって隠されているコードに非常に腹を立てます。

したがって、この場合gotoは問題ありませんが、マクロは問題ありません。

于 2009-03-31T09:11:18.517 に答える
8

goto一般的なエラー ハンドラ/クリーンアップ/終了シーケンスに移動するために使用しても問題ありません。

于 2009-03-31T08:59:25.770 に答える
7

このコード:

void func()
{
   char* p1 = malloc(16);
   if( !p1 )
      goto cleanup;

   char* p2 = malloc(16);
   if( !p2 )
      goto cleanup;

 cleanup:

   if( p1 )
      free(p1);

   if( p2 )
      free(p2);
}

法的には次のように書くことができます:

void func()
{
   char* p1 = malloc(16);
   char* p2 = malloc(16);

    free(p1);
    free(p2);
}

メモリ割り当てが成功するかどうか。

これは、NULL ポインターが渡された場合に free() が何もしないためです。独自の API を設計して他のリソースを割り当てたり解放したりするときに、同じイディオムを使用できます。

// return handle to new Foo resource, or 0 if allocation failed
FOO_HANDLE AllocFoo();

// release Foo indicated by handle, - do nothing if handle is 0
void ReleaseFoo( FOO_HANDLE h );

このような API を設計すると、リソース管理が大幅に簡素化されます。

于 2009-03-31T09:01:50.303 に答える
3

最初の malloc が失敗した場合は、p1 と p2 の両方をクリーンアップします。goto のため、p2 は初期化されておらず、何かを指している可能性があります。これを gcc ですばやく実行して確認しましたが、free(p2) を試行すると実際にセグ フォールトが発生します。

最後の例では、変数は中括弧内にスコープされています (つまり、変数は FAIL_SECTION_BEGIN ブロックにのみ存在します)。

コードがブレースなしで機能すると仮定すると、セグ フォールトを回避するために、FAIL_SECTION_BEGIN の前にすべてのポインターを NULL に初期化する必要があります。

私は goto とマクロに反対するものは何もありませんが、Neil Butterworth のアイデアの方が好きです..

void func(void)
{
    void *p1 = malloc(16);
    void *p2 = malloc(16);
    void *p3 = malloc(16);

    if (!p1 || !p2 || !p3) goto cleanup;

    /* ... */

cleanup:
    if (p1) free(p1);
    if (p2) free(p2);
    if (p3) free(p3);
}

または、より適切な場合..

void func(void)
{
    void *p1 = NULL;
    void *p2 = NULL;
    void *p3 = NULL;

    p1 = malloc(16);
    if (!p1) goto cleanup;

    p2 = malloc(16);
    if (!p2) goto cleanup;

    p3 = malloc(16);
    if (!p3) goto cleanup;

    /* ... */

cleanup:
    if (p1) free(p1);
    if (p2) free(p2);
    if (p3) free(p3);
}
于 2009-03-31T13:21:41.457 に答える
3

Cleanup withgotoは一般的な C のイディオムであり、Linux カーネルで使用されます*。

**おそらく、Linus の意見は適切な議論の最良の例ではありませんがgoto、比較的大規模なプロジェクトで使用されていることを示しています。

于 2009-03-31T09:08:52.283 に答える
2

元のコードは、複数の return ステートメントを使用することでメリットが得られます。エラー リターン クリーンアップ コードを回避する必要はありません。さらに、通常、通常のリターンでも解放された割り当てられたスペースが必要です。そうしないと、メモリリークが発生します。そして、goto注意すれば例を書き直すことができます。これは、他の方法で必要になる前に変数を便利に宣言できる場合です。

void func()
{
    char *p1 = 0;
    char *p2 = 0;
    char *p3 = 0;

    if ((p1 = malloc(16)) != 0 &&
        (p2 = malloc(16)) != 0 &&
        (p3 = malloc(16)) != 0)
    {
        // Use p1, p2, p3 ...
    }
    free(p1);
    free(p2);
    free(p3);
}

各割り当て操作の後に自明ではない量の作業がある場合、最初のfree()操作の前にラベルを使用できます。これで問題ありません。最近goto使用する主な理由はエラー処理であり、gotoそれ以外はやや疑わしいものです。 .

私は、goto ステートメントが埋め込まれたマクロを含むいくつかのコードを管理しています。可視コードによって「参照されていない」にもかかわらず、削除できないラベルを最初に見たとき、混乱を招きます。私はそのような慣習を避けることを好みます。マクロは、マクロが何をするかを知る必要がないときは問題ありません。マクロを正確に使用するために展開先を知る必要がある場合、マクロはあまり適切ではありません。彼らが私から情報を隠していなければ、彼らは助けというより迷惑です。

写真素材 - 有罪を保護するために偽装された名前:

#define rerrcheck if (currval != &localval && globvar->currtub &&          \
                    globvar->currtub->te_flags & TE_ABORT)                 \
                    { if (globvar->currtub->te_state)                      \
                         globvar->currtub->te_state->ts_flags |= TS_FAILED;\
                      else                                                 \
                         delete_tub_name(globvar->currtub->te_name);       \
                      goto failure;                                        \
                    }


#define rgetunsigned(b) {if (_iincnt>=2)  \
                           {_iinptr+=2;_iincnt-=2;b = ldunsigned(_iinptr-2);} \
                         else {b = _igetunsigned(); rerrcheck}}

rgetunsigned()似たような数十のバリアントがあります- サイズが異なり、ローダー機能が異なります。

これらが使用される 1 つの場所には、次のループが含まれます。コードの小さなブロックと大きなコード ブロックがいくつかある大きなスイッチの 1 つのケースの大きなコード ブロック (特に適切に構造化されていません):

        for (i = 0 ; i < no_of_rows; i++)
            {
            row_t *tmprow = &val->v_coll.cl_typeinfo->clt_rows[i];

            rgetint(tmprow->seqno);
            rgetint(tmprow->level_no);
            rgetint(tmprow->parent_no);
            rgetint(tmprow->fieldnmlen);
            rgetpbuf(tmprow->fieldname, IDENTSIZE);
            rgetint(tmprow->field_no);
            rgetint(tmprow->type);
            rgetint(tmprow->length);
            rgetlong(tmprow->xid);
            rgetint(tmprow->flags);
            rgetint(tmprow->xtype_nm_len);
            rgetpbuf(tmprow->xtype_name, IDENTSIZE);
            rgetint(tmprow->xtype_owner_len);
            rgetpbuf(tmprow->xtype_owner_name, IDENTSIZE);
            rgetpbuf(tmprow->xtype_owner_name,
                     tmprow->xtype_owner_len);
            rgetint(tmprow->alignment);
            rgetlong(tmprow->sourcetype);
            }

そこのコードに goto ステートメントが含まれていることは明らかではありません。そして明らかに、それが由来するコードの罪を完全に解釈するには、一日中かかるでしょう - それらは多種多様です。

于 2009-03-31T13:31:07.440 に答える
2

アンチ goto として誰もが知っている「構造化プログラミング」という用語は、もともと goto (または JMP) を使用した一連のコーディング パターンとして始まり、発展しました。これらのパターンはwhileandifパターンなどと呼ばれていました。

そのため、goto を使用している場合は、構造化された方法で使用してください。それはダメージを制限します。そして、これらのマクロは合理的なアプローチのようです。

于 2009-03-31T09:06:20.637 に答える
1

最初の例は、マクロ化されたバージョンよりもはるかに読みやすいように見えます。ムヴィシエルは私よりずっと上手に言った

于 2009-03-31T09:02:48.880 に答える
0
#define malloc_or_die(size) if(malloc(size) == NULL) exit(1)

トランザクションシステムを作成する価値のあるソフトウェアがない限り、失敗したmallocから実際に回復できるわけではありません。回復する場合は、malloc_or_dieにロールバックコードを追加してください。

gotoの適切な使用例については、計算されたgotoを使用するディスパッチコードの解析を確認してください。

于 2009-04-23T03:31:34.177 に答える