【问题标题】:What is the correct way to implement error propagation in C?在 C 中实现错误传播的正确方法是什么?
【发布时间】:2015-09-10 17:32:41
【问题描述】:

我来自带有异常的高级语言(尤其是 C#),我通常只捕获我的逻辑知道如何处理的异常——否则,它们会传播并导致程序崩溃,但至少它提供了关于发生了什么,在哪里。我想在 C 中实现类似的行为。我想在我的 main 的小主体中,从我调用的函数中获取错误代码,以确定发生了什么,并将错误消息打印到 stderr 并退出与我得到的错误代码相同。

  1. 第一个问题——人们是否在 C 语言中这样做?就架构而言,这是一个好的目标吗?

  2. Seconds 问题是关于错误返回码和 errno。默认情况下我应该在我的代码中使用什么?看起来这两种约定在 C 标准库中可以互换使用,很混乱。

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

  4. 最后,关于错误的附加信息:我发现,至少在 C# 中,能够使用提供附加信息的特定注释抛出预定义类型的异常非常有用。 C 错误管理中是否使用了类似的东西?我应该如何实现它?

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

【问题讨论】:

  • 如果您只想打印错误消息并退出,那么main 看起来不适合这样做。编写一个错误处理函数,并在发生致命错误时从您检测到的地方调用它。 OTOH,如果您想清理并继续,您可能需要更多参与。
  • 无论谁将此问题标记为“过于宽泛”,我很想听听您对为什么它过于宽泛以及如何在这方面改进这个问题的想法。

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


【解决方案1】:

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

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

它通常用于此目的吗?

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

现在,对于用户定义的函数,您可以为库函数返回的错误创建自己的错误代码(需要一些映射时间),并且您可以从函数返回自己的值。


编辑:

同时使用有什么好处?

在发生故障事件时,以简单的方式区分故障原因。

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

而是使用固定的返回值来表示错误,如果错误,则检查errno以了解错误背后的原因。

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

【讨论】:

  • 我知道我可以;但问题是,它通常用于此目的吗?经验丰富的 C 开发人员如何决定是否使用 errno?
  • 好的,分歧背后的原因是什么?有什么反逻辑吗?评论否决?
  • "你需要多少 if-else 或 switch 语句来检查被调用函数是否成功?"一、if (dostuff() == 0).
【解决方案2】:

关于:我想在我的 main 的小主体中,从我调用的函数中获取错误代码,以确定发生了什么,并将错误消息打印到 stderr 并使用相同的错误代码退出正如我得到的

第一个问题——人们会在 C 语言中这样做吗,这是一个好的目标吗? 就建筑而言,完全没有?

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

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

查看第一个答案。它很好地涵盖了这一点。 (+1 给@Sourav)

第三个问题是关于错误代码本身。在 C# 和 Java 中,有 是标准的异常类型,几乎涵盖了您想要的所有内容 除了特定的 cmets,通常会抛出。我应该使用 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 报道。

【讨论】:

  • “成功条件的正值”——这不违反 C 中成功返回 0 的约定吗?失败的方式很多,成功的方式只有一种,这对我来说似乎是一个非常合理的惯例。
  • “解决上一个问题的评论也解决了这个问题。”我不认为我们在这里谈论的是同一件事。您似乎在谈论一个看起来像“文件读取错误”的一般错误描述字符串,而我正在谈论一个带有特定信息的字符串,例如“在 15:24:34 从 open_file() 函数读取 mystuff.txt 时出错”。错误代码FILE_OPEN_ERROR 是一样的,但我也想捕获细节。
  • 但是感谢您的回答——这正是我正在寻找的答案的类型
  • @MaxYankov - 第一条评论 - 是的,对于这个特定的 API,我选择了一个约定,并通过头文件发布了它。标准 C 库以外的 API 尚未确定正/负的硬性约定。各自建立了自己的。这完全取决于开发人员。这就是为什么我将所有库的错误条件合并到一个异常处理表中,以向 API 的用户呈现一组一致的错误条件仅针对 API
  • @MaxYankov - 第二条评论 - 当我想要有关异常的更多详细信息(例如时间戳)时,我使用字符串表示形式,例如来自 GetErrorDescription() 函数,并将其传递给日志记录例程,我可以在其中打包一个日志条目以包含 API 错误描述、时间戳和任何其他相关信息,例如此错误发生在哪个调用函数中。
【解决方案3】:

一般来说,异常类型的行为不是在 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));
  }
}

【讨论】:

  • setjmp/longjmp 是纯 C,因此无需将其限制为 POSIX 系统。
  • 我认为你没有仔细阅读这个问题。我不打算在 C 中实现异常;我只是在询问错误传播。
  • @JensGustedt 谢谢,我从回答中删除了限制。
  • @MaxYankov 如果您想将错误传播回 main 进行处理,无论何时调用一个函数,您都需要检查返回值并将该值返回给它的调用者。因此,除了调用函数返回的错误值之外,每个函数都需要跟踪自己的正常返回值。使用setjmp()longjmp(),您无需担心这些。
猜你喜欢
  • 1970-01-01
  • 2016-03-01
  • 1970-01-01
  • 2011-11-06
  • 2018-08-12
  • 1970-01-01
  • 2016-08-31
  • 2014-04-07
  • 1970-01-01
相关资源
最近更新 更多