在C中实现错误传播的正确方法是什么?

时间:2015-06-24 13:23:52

标签: c exception error-handling error-code return-code

我来自具有异常的高级语言(特别是C#),我通常只捕获我的逻辑知道如何处理的异常 - 否则,它们会传播和编程崩溃,但至少它提供了一个明确的信息发生了什么,在哪里。我想在C中实现类似的行为。我希望在我的main的小体中,从我调用的函数中获取错误代码,以识别发生的事情,并将错误消息打印到{{1并使用与我相同的错误代码退出。

  1. 第一个问题 - 人们是否在C中做到这一点,在建筑方面,这是一个很好的目标吗?

  2. 秒问题是关于错误返回码和错误。默认情况下我应该在代码中使用什么?似乎这两个约定在C标准库中可以互换使用,而且非常令人困惑。

  3. 第三个问题是关于错误代码本身。在C#和Java中,除了特定的注释之外,还有标准的异常类型几乎涵盖了您通常会抛出的所有内容。我应该为此目的使用errno错误代码表,还是应该为我的应用程序创建一个单独的错误代码表以包含它特定的所有内容?或者可能每个功能都应该维护它自己的特定错误代码表?

  4. 最后,有关错误的其他信息:我发现在C#中,至少,能够使用提供附加信息的特定注释抛出预定义类型的异常非常有用。是这样的,在C错误管理中使用?我该如何实施呢?

  5. 为什么库函数同时使用返回码 errno而不是坚持使用其中一个?使用两者的好处是什么?

3 个答案:

答案 0 :(得分:3)

看来,您可以使用errno变量和perror()函数。它们有助于提供大多数时间失败的原因。

为了指示失败,大多数库函数通常会返回一个带前缀的负值(主要是-1),并将errno变量设置为特定值,取决于错误的类型。

  

通常用于此目的吗?

是的,这就是拥有errnoperror()的目的,恕我直言。

现在,如果是用户定义的函数,您可以为库函数返回的错误创建自己的错误代码(有一些映射时间),您可以从函数中返回自己的值。

编辑:

  

使用两者有什么好处?

在失败事件的情况下,以简单的方式区分失败的原因。

这样想,而不是为错误和设置errno设置一个固定的reurn值,如果函数为每种类型的错误返回不同的值,那么你有多少if-elseswitch语句每次调用时,是否需要检查被调用函数的成功?这太棒了。

相反,使用固定的返回值来指示错误和错误,然后检查错误以找出错误背后的原因。

[希望我很清楚。英语不是我的母语,抱歉]

答案 1 :(得分:2)

关于:我希望,在我的主体的小体中,从我调用的函数中获取错误代码,以识别发生的事情,并将错误消息打印到stderr并使用相同的错误代码退出因为我

  

第一个问题 - 人们是否在C中这样做,这是一个很好的目标,   在建筑方面,根本没有?

有些 的人会这样做,但 所有 的人应该这样做。它被认为是好形式

  

秒问题是关于错误返回码和错误。我应该怎么   默认情况下在我的代码中使用?似乎这两个惯例是   在C标准库中可以互换使用,而且非常令人困惑。

见第一个答案。它涵盖了这一点。 (+1来自@Sourav)

  

第三个问题是关于错误代码本身。在C#和Java中,有   是标准的异常类型,几乎涵盖了你想要的一切   除了特定的评论之外,通常会抛出。我应该使用errno吗?   为此目的的错误代码表,或者我应该创建一个单独的错误   我的应用程序的代码表,包括它特定的所有东西   它?或者可能每个功能都应该维护它自己的特定表   错误代码?

这完全取决于开发者 。在创建应用程序时,我将使用生成条件的库调用本机的特定错误代码/消息。当我创建一个API(通常是.dll)时,我经常会创建一个包含特定于应用程序的所有可能返回条件的枚举,并且还会将C本机库错误集成到此枚举中。成功条件的零/正值,错误条件的负值。与此同时,我创建了一个对应于每个返回条件的字符串描述数组。这些可由应用程序通过函数调用,例如。 int GetErrorDescription(int error, char *str);。但是,这可以采用几种不同的方式,这只是我的方法。

示例:

/*------------------------------------------
//List of published error codes
/*-----------------------------------------*/
enum    {
    SUCCESS                        =  0,
    COPYFILE_ERR_1                 = -1,   //"CopyFile() error. File not found or directory in path not found.",
    COPYFILE_ERR_3                 = -2,   //"CopyFile() error. General I/O error occurred.",
    COPYFILE_ERR_4                 = -3,   //"CopyFile() error. Insufficient memory to complete operation.",
    COPYFILE_ERR_5                 = -4,   //"CopyFile() error. Invalid path or target and source are same.",
    COPYFILE_ERR_6                 = -5,   //"CopyFile() error. Access denied.",
    ...
    FOPEN_ERR_EIO                  = -11,  //"fopen() error. I/O error.",
    FOPEN_ERR_EBADF                = -12,  //"fopen() error. Bad file handle.",
    FOPEN_ERR_ENOMEM               = -13,  //"fopen() error. Insufficient memory.",
    ...
    GETFILESIZE_ERR_3              = -24,  //"GetFileSize() error - Insufficient memory to complete operation.",
    GETFILESIZE_ERR_4              = -25,  //"GetFileSize() error - Invalid path or target and source are same.",
    GETFILESIZE_ERR_5              = -26,  //"GetFileSize() error - Access denied.",
    ...
    PATH_MUST_BE_C_DRIVE           = -38,  //"SaveFile path variable must contain \"c:\\\".",
    HEADER_FIELD_EMPTY             = -39,  //"one or more of the header fields are incorrect or empty.",
    HEADER_FIELD_ILLEGAL_COMMA     = -40,  //"one or more of the header fields contains a comma.",
    ...
    MAX_ERR                        =  46   // defines the size of the static char ErrMsg
};

然后是获取说明的函数:

int API GetErrorMessage (int err, char * retStr)
{
    //verify arguments are not NULL
    if(retStr == NULL)
    {
        return UNINIT_POINTER_ARGUMENT;
    }
    switch(err) {
        case  COPYFILE_ERR_1               : strcpy(retStr, "CopyFile() error. File not found or directory in path not found."     ); break;
        case  COPYFILE_ERR_3               : strcpy(retStr, "CopyFile() error. General I/O error occurred."                        ); break;
        case  COPYFILE_ERR_4               : strcpy(retStr, "CopyFile() error. Insufficient memory to complete operation."         ); break;
        case  COPYFILE_ERR_5               : strcpy(retStr, "CopyFile() error. Invalid path or target and source are same."        ); break;
        case  COPYFILE_ERR_6               : strcpy(retStr, "CopyFile() error. Access denied."                                     ); break;
        ...
        case  FOPEN_ERR_EIO                : strcpy(retStr, "fopen() error. I/O error."                                            ); break;
        case  FOPEN_ERR_EBADF              : strcpy(retStr, "fopen() error. Bad file handle."                                      ); break;
        case  FOPEN_ERR_ENOMEM             : strcpy(retStr, "fopen() error. Insufficient memory."                                  ); break;
        ...
        case  GETFILESIZE_ERR_3            : strcpy(retStr, "GetFileSize() error - Insufficient memory to complete operation."     ); break;
        case  GETFILESIZE_ERR_4            : strcpy(retStr, "GetFileSize() error - Invalid path or target and source are same."    ); break;
        case  GETFILESIZE_ERR_5            : strcpy(retStr, "GetFileSize() error - Access denied."                                 ); break;
        ...
        case  HEADER_FIELD_EMPTY           : strcpy(retStr, " one or more of the header fields are incorrect or empty."        ); break;
        case  HEADER_FIELD_ILLEGAL_COMMA   : strcpy(retStr, " one or more of the header fields contains a comma."              ); break;
        case  EVENT_FIELD_EMPTY            : strcpy(retStr, " one or more of the event fields is incorrect or empty."          ); break;
        case  EVENT_FIELD_ILLEGAL_COMMA    : strcpy(retStr, " one or more of the event fields contains a comma."               ); break;
        ...
        default:   strcpy(retStr, ""); break;
    }
    return 0;
}
  

最后,有关错误的其他信息:我发现在C#中,   至少,能够抛出异常是非常有用的   具有特定注释的预定义类型,提供额外的   信息。是这样的,在C错误管理中使用?怎么样   我应该实施吗?

评论解决上一个问题 也解决了这个问题。

  

为什么库函数同时使用返回码和errno   而不是坚持一个或另一个?使用的好处是什么?   既?

再次 ,由Sourav承保。

答案 2 :(得分:0)

一般来说,异常类型的行为不是用C语言完成的。惯例是每个函数的返回值都应该由直接调用者和任何辅助变量(如errno)检查和处理。 )该功能可以设置。

话虽如此,您可以使用setjmp()longjmp()执行与例外类似的操作。您使用setjmp()设置跳转点,传入jmp_buf结构以保存跳转点。稍后在代码中,无论调用堆栈多远,您都可以将jmpbuf传递给longjmp(),这将导致控制返回到调用setjmp()的位置。您还会将值传递给longjmp(),这将是setjmp()的返回值。然后,您可以将此值用作查找错误表以打印更详细的消息。

您可以阅读与longjmp()相关的several其他问题。

编辑:

一个例子:

jmp_buf jbuf;

void some_function()
{
   ...
   if (some_error()) {
     longjmp(jbuf, 2);
     /* control never gets here */
   }
   ...
}

void run_program()
{
  ...
  some_function();
  ...
}

int main()
{
  int rval;
  if ((rval=setjmp(jbuf)) == 0) {
    run_program();
  } else {
    /* rval will be 2 if the error in some_function() is triggered */
    printf("error %d: %s\n", rval, get_err_string(rval));
  }
}