3

失敗の可能性がかなり高い、小さくて特定のタスクを実行する関数があるとしましょう。何かがうまくいかないことに対処するための最良の方法は何ですか?(私が問題が何であるかを知っていると仮定します)。

たとえば、2バイトの文字列を読み取って返す関数があるとします。

#include <stdio.h>
#include <stdlib.h>

char *bar(void)
{
    char *foo = malloc(3);
    scanf("%2s", foo);
    return foo;
}

int main(void)
{
    char *foo = bar();
    puts(foo);
    free(foo);
    return 0;
}

上記の例では、エラー処理はまったくありません。ある種のエラー処理を実装する方法は2つありますが、どちらがより好ましいか、またはベストプラクティスと見なされるかはわかりません。

方法1(関数内からstderrにエラーメッセージを出力する):

#include <stdio.h>
#include <stdlib.h>

char *bar(void)
{
    char *foo;
    if(!(foo = malloc(3)))
    {
        fputs("\nError! Memory allocation failed.", stderr);
        return 0x00;
    }
    scanf("%2s", foo);
    return foo;
}

int main(void)
{
    char *foo;
    if(!(foo = bar())) return 1;
    puts(foo);
    free(foo);
    return 0;
}

方法2(呼び出し元の関数からstderrにエラーメッセージを出力する):

#include <stdio.h>
#include <stdlib.h>

char *bar(void)
{
    char *foo;
    if(!(foo = malloc(3))) return 0x00;
    scanf("%2s", foo);
    return foo;
}

int main(void)
{
    char *foo;
    if(!(foo = bar()))
    {
        fputs("\nError! Memory allocation failed.", stderr); 
        return 1;
    }
    puts(foo);
    free(foo);
    return 0;
}

方法2が最善の方法だとほぼ考えています。そうすれば、その時点でその関数を何に呼んでいるかに応じて、エラーメッセージをより具体的にすることができるからです。方法2で私が心配しているのは、関数に複数の潜在的な障害点がある場合、関数で特に問題が発生したものを印刷する機能が失われるという事実です。

擬似コード:

 IF FAILUREA
     PRINT "FAILUREA OCCURED"
     RETURN
 IF FAILUREB
     PRINT  "FAILUREB OCCURED"
     RETURN

私が呼び出していた関数が原因である場合、これはそれほど問題にはなりません。問題が発生したintことに基づいて、異なる整数値を返すことができるからです。しかし、Iの場合、char*私は通常NULL、失敗時に復帰しようとします(したがって、両方FAILUREAFAILUREBが復帰しNULLます)。関数が失敗した原因を知る方法はありません。

だから私の質問は、エラーメッセージの処理に関してベストプラクティスは何ですか?

4

3 に答える 3

2

呼び出し元がエラー報告を処理できるようにすることは、次の理由により優れています。

  • 関数がライブラリの一部を形成している場合はstderr使用できない可能性があり、代替のレポートメカニズムが必要です。
  • 呼び出し元のコードには、実行可能な代替アクションがあり、機能の障害をbar()実際の障害と見なさず、報告する必要がない場合があります。

関数に複数の考えられる失敗の理由がある場合、失敗の場合に更新される関数に引数を渡す可能性があります。呼び出し元の関数は、実際の失敗の理由に応じて適切なアクションを選択できます。例えば:

enum Status
{
    STATUS_OK,
    STATUS_MEMORY_ALLOCATION_FAILURE,
    STATUS_ACCESS_DENIED
};

enum Status status;
char* foo = bar(&status);
if (!foo)
{
    if (STATUS_MEMORY_ALLOCATION_FAILURE == status)
    {
        /* report failure. */
    }
    else if (STATUS_ACCESS_DENIED == status)
    {
        /* try somewhere else */
    }
}
于 2012-10-10T13:41:29.743 に答える
2

あなたが失敗について何かをすることができて、あなたがそうしようとしているなら、あなたはそれをします。それ以外の場合は、一般的な障害関数を実装し、エラーが発生した場合に呼び出して、1日で呼び出すことができます。

void error(const char* format, ...)
{
  va_list vl;
  va_start(vl, format);
  vfprintf(stderr, format, vl);
  va_end(vl);
  exit(-1);
}

オプションで、行番号とファイル名を指定してマクロでラップできます。

#define ERROR(fmt, ...) \
  error("file:'%s',line:%d " fmt, __FILE__, __LINE__, __VA_ARGS__)

これにより、エラーメッセージがファイルとその中のエラーが発生した行を正確に伝えるため、コンソールのエラーを非常に簡単に把握できます。

典型的な使用法、派手なものは何もありません:

char *bar(void)
{
  char *foo;
  if ((foo=malloc(3)) == NULL)
    ERROR("malloc() failed!\n");
  if (scanf("%2s", foo) != 1)
    ERROR("scanf() failed!\n");
  return foo;
}

エラー時に実際に何かを実行したい場合はlongjmp()、代わりにを使用しexit(-1)て、呼び出し元(=それぞれを実行した呼び出し元)にすぐに戻ることができます。書き込み用に開いているすべてのファイルを閉じて、バッファリングされたデータが失われないようにします。setjmp()

たとえば、単純なコンパイラを作成している場合、この種のerror()エラーは、コンパイラ内部のほとんどのエラーや、コンパイルされるソースコードの問題(たとえば、コロン/パレンの欠落など、コードを機能させないもの)には十分です。コンパイル可能)。

それができない、またはしたくない場合は、コードを注意深く記述し、適切なクリーンアップを実行し、さまざまなエラーコードを返して、実行可能なエラーを呼び出し元に伝える必要があります。

于 2012-10-10T13:56:50.267 に答える
1

関数が複数のエラーケースを返す場合は、この方法で行うことができます

#include <stdio.h>
#include <stdlib.h>

int bar(char **foo)
{
    if(!(malloc(3))) return 1; /* return error case 1*/
    scanf("%2s", *foo);
    if(!(malloc(4))) return 2; /* return error case 2*/
    return 0; /* no error*/
}

int catcherror(int error)
{
    switch (error) {
         case 1: 
             /*do something 1*/
         case 2: 
             /*do something 1*/
         case 3: 
             /*do something 1*/
         case 4: 
             /*do something 1*/
         case 5: 
             /*do something 1*/
         default: 
             /*do something 1*/
     }
}

int main(void)
{
    char *foo;
    int error

    error = bar(&foo);
    catcherror(error);
    puts(foo);
    free(foo);
    return 0;
}

catcherror()プロジェクトに一般的なエラーケースを返す多くの関数が含まれている場合、この関数は非常に便利です。

于 2012-10-10T13:51:53.953 に答える