web-dev-qa-db-ja.com

「check-log-return」を使用してCでエラーを処理する:マクロを使用しないのはなぜですか?

Cでは、次のようなエラーを処理することをお勧めします(私はそう思います)。

int status = tree_climb(tree, ...);
if (status != 0) {
    global_logger.message(2, "Cannot climb a tree %s", tree->name);
    return EPIPE;
}

または、代わりに

forest_errno = tree_climb(tree, ...);
if (forest_errno != 0) {
    goto exit;
}

exit:
if (tree) {
    tree_dispose(tree);
}
if (forest) {
    forest_dispose(forest);
}

return forest_errno;

質問は:

これにプリプロセッサマクロを使用しない理由は何ですか?次のようなコードをたくさん目にしたことを思い出せません。

#define CHECK_OR_RETURN(contract, error_status, log_level, message_format, ...) \
if (!(contract)) { \
    global_logger.message(log_level, message_format, ##__VA_ARGS__); \
    return error_status; \
}

マクロを使用する理由は次のとおりです。

  • これにより、4行のコード(通常の操作の流れの中で何もしない3行)が1行に縮小されます。
  • エラー処理は、理想的には、通常のフローのコードを汚染しないようにする必要があります。

マクロを使用しない理由は次のとおりです。

  • マクロは、コードの読み取りとデバッグをやや鈍らせます。独自に開発した「魔法の」構文は、使い慣れた言語を理解できないものに変える可能性があります。場合によっては、ライブラリの詳細を知らないと、コードを解析できないことがあります。ただし、大規模な場合、3倍短いコードは9倍読みやすくなります。
  • C++では、例外を使用できます。チェックされていない例外を使用したくないのですが、私の質問の範囲ではありません。
  • 一部の人々はコード長を気にしません。

なぜ人々はこれをいつもしませんか?

3

「代替」は、Cコードがリソースの段階的な取得を実装する可能性があることを示しています

エラー処理を行わずに次の例を見てみましょう。ここでインデントを使用して、さまざまな手順を示します。

 FILE *fp = fopen("data.txt", "rb");          // step 1: acquire file resource
   Forest* forest = generate_forest(fp,...);    // step 2: allocate tree resource 
     Tree *tree = get_tree(forest, "SPAIN"...);   
     Element *element = malloc(sizeof(element));  // step 3: allocate new element
       element->value = MAGIC; 
       tree_add(tree, element);                 // suppose element is copied into the tree
     free (element);                          // end step 3 
     ...
   release_forest (forest);                 // end step 2 (forest takes care of its trees)
   ...
 fclose (fp);                             // end step 1

ここでは、各単一の命令によってエラーが発生する可能性があります。エラーが発生するステップによっては、一部または複数のリソースを解放する必要がある場合があります。

代替1

マクロを使用してさまざまなエラーをログに記録して処理すると、すべてのリソースが解放されるわけではないのに、リソースをリークするだけです。

代替2

いくつかのネストされた条件ステートメントを使用する:

 FILE *fp = fopen("data.txt", "rb");          // step 1: acquire file resource
 if (fp) {                                    // go on for step 1
     Forest* forest = generate_forest(fp,...);    // step 2: allocate tree resource 
     if (forest) {                                // go on for step 2
        Tree *tree = get_tree(forest, "SPAIN"...);   
        ...
        release_forest (forest);                  // end step 2 (forest takes  
     } 
     else {
        errcode = logmyerror ("forest not properly loaded", ENOTFOUND);
     }
     fclose (fp);                             // end step 1
 }
 else {
     errcode = logmyerror ("file not found ", EOOPSAGAIN);
 }
 return errcode;   // final exit point 

代替3

ignomous goto (ご覧のとおり、いくつかの利点があります)。ここで、さまざまなラベルが、関連する手順の出口点に対応しています。

 FILE *fp = fopen("data.txt", "rb");          // step 1: acquire file resource
 if (fp==NULL) {
     errcode = logmyerror ("file not found ", EOOPSAGAIN);
     goto out0; 
 }
 Forest* forest = generate_forest(fp,...);    // step 2: allocate tree resource 
 if (forest==NULL) { 
     errcode = logmyerror ("forest not properly loaded", ENOTFOUND);
     goto out1; 
 }  
 Tree *tree = get_tree(forest, "SPAIN"...);   
 ... 
out2: 
 release_forest (forest);                 // end step 2 (forest takes care of its trees)
   ...
out1: 
 fclose (fp);                             // end step 1

out0: 
 return errcode; 

結論

もちろん、そのようなマルチレベルのリソース割り当てがない場合は、マクロを完全に使用できます。しかし、現実にはこれでは不十分な状況がたくさんあると思います。

C++では、RAIIを使用する場合、これは必要ないことに注意してください。デストラクタは、戻るときに常にリソースをクリーンアップします。

4
Christophe

あなたがリストした理由のために人々はしません。さらに、一般的な「適切なコーディングプラクティス」はありませんが、最良の場合には、いくつかの部門のルールがあります。また、一貫性のないコードがたくさんあります。つまり、パーツはマクロを使用し、パーツは使用しません。おそらく、どのプログラマーが手にしたかに依存します。

だから私はどのアドバイスを与えることができますか?これらの単純なエラー処理スニペットがたくさんある場合は、マクロを自由に使用してください。密集したコードは、特にそれらの反復的なスニペットでは、散在するよりも読む方が良いと思います。

もちろん、より複雑なエラー処理がある場合は、管理できないマクロになる可能性があります。

2
qwerty_so

Cで例外がない場合は、「ロングジャンプ」で例外をエミュレートできます。このようにして、try/catchのように、例外を処理するための1つのポイントを持つことができ、このポイントをより高いスタックレベルにすることもできます。

以下は実装例ですが、基本(longjmp/setjmp)は最初からCの一部であり、追加のソフトウェアは必要ありません。 http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html

0
rplantiko