简体   繁体   English

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

[英]What is the correct way to implement error propagation in C?

I come from high-level languages with exceptions (C#, in particular), and I usually only catch exceptions that my logic knows how to handle — otherwise, they propagate and program crashes, but at least it provides a clear information about what happened, and where. 我来自具有异常(特别是C#)的高级语言,并且通常只捕获我的逻辑知道如何处理的异常-否则,它们会传播并导致程序崩溃,但至少它提供了有关发生的情况的清晰信息,在哪里。 I want to implement similar behavior in C. I want, in the small body of my main , to get an error code from the functions that I call, to identify what happened, and to print error message to stderr and quit with the same error code as I got. 我想在C中实现类似的行为。我想在main的小部分中,从调用的函数中获取错误代码,以识别发生了什么,并将错误消息打印到stderr并退出并返回相同的错误我得到的代码。

  1. First question — do people do that in C, and is it a good objective, in terms of architecture, at all? 第一个问题-人们是否使用C语言做到这一点,就架构而言,这是否是一个好的目标?

  2. Seconds question is about error return codes and errno. 第二个问题是关于错误返回码和错误号。 What should I use in my code by default? 默认情况下,我应该在代码中使用什么? It seems that these two conventions are used interchangeably in C standard library, and it's pretty confusing. 似乎这两个约定在C标准库中可以互换使用,这非常令人困惑。

  3. Third question is about error codes themselves. 第三个问题是关于错误代码本身。 In C# and Java, there are standard exception types that cover almost everything you would typically throw, apart from specific comments. 在C#和Java中,除了特定的注释外,标准的异常类型几乎涵盖了您通常会抛出的所有内容。 Should I use errno error code table for this purpose, or should I create a separate error code table for my application to include all stuff that it specific to it? 我应该为此目的使用errno错误代码表,还是应该为我的应用程序创建一个单独的错误代码表以包含所有特定于它的东西? Or may be every function should maintain it's own specific table of error codes? 还是每个函数都应该维护自己的特定错误代码表?

  4. And finally, additional information about errors: I found that in C#, at least, it's very useful to be able to throw an exception of predefined type with a specific comment that provides additional information. 最后,关于错误的其他信息:我发现,至少在C#中,能够抛出预定义类型的异常并带有提供更多信息的特定注释非常有用。 Is something like this used in C error management? C错误管理中使用了类似的东西吗? How should I implement it? 我应该如何实施?

  5. Why do library functions use return codes and errno at the same time instead of sticking to one or the other? 为什么库函数同时使用返回码 errno而不是一个或另一个? What's the benefit in using both? 同时使用两者有什么好处?

It seems, you can make use of errno variable and perror() functions. 看来,您可以使用errno变量和perror()函数。 They help to provide the reason for failure for most of the time. 大多数情况下,它们有助于提供失败原因。

Most of the library functions, to indicate the failure, will usually return a prefixed negative value (mostly -1 ) and set the errno variable to a particular value, depending on the type of the error. 为了指示失败,大多数库函数通常会返回带前缀的负值(通常为-1 ),并将errno变量设置为特定值,具体取决于错误的类型。

is it normally used for this purpose? 通常用于此目的吗?

Yes, that is the purpose for having errno and perror() , IMHO. 是的,这就是拥有errnoperror()的目的,恕我直言。

Now, in case of user-defined functions, you can create own error codes for the errors returned by the library functions (with some time of mapping) and you can return your own value from your function. 现在,对于用户定义的函数,您可以为库函数返回的错误创建自己的错误代码(需要一段时间的映射),并且可以从函数中返回自己的值。


EDIT: 编辑:

What's the benefit in using both? 同时使用两者有什么好处?

To differentiate the reason for failure, in case of a failure event, in an easy way. 在发生故障事件时,以一种简单的方式来区分故障原因。

Think it like this, instead of having a fixed reurn value for error and setting errno, if a function returns different values for each type of error, then how many if-else or switch statements you'll need for checking the success of the called function, each time it's called? 像这样思考,而不是为错误设置固定的reurn值并设置errno,如果函数针对每种类型的错误返回不同的值,则需要多少个if-elseswitch语句来检查被调用函数是否成功函数,每次调用? It's huge. 很大。

Rather, use a fixed return value to indicate error and if error, then check the errno for the reason behind the error. 而是使用固定的返回值指示错误,如果有错误,则检查errno以了解错误背后的原因。

[Hope I'm clear. [希望我很清楚。 English is not my native language, sorry] 英语不是我的母语,对不起]

Regarding: I want, in the small body of my main, to get an error code from the functions that I call, to identify what happened, and to print error message to stderr and quit with the same error code as I got 关于: 我想在我主体的小部分中,从调用的函数中获取错误代码,确定发生了什么,并将错误消息打印到stderr并使用与我得到的相同的错误代码退出

First question — do people do that in C, and is it a good objective, in terms of architecture, at all? 第一个问题-人们是否使用C语言做到这一点,就架构而言,这是否是一个好的目标?

Some people do this, but All people should. 有人这样做,但所有人都应该这样做。 It is considered good form 被认为是好的形式

Seconds question is about error return codes and errno. 第二个问题是关于错误返回码和错误号。 What should I use in my code by default? 默认情况下,我应该在代码中使用什么? It seems that these two conventions are used interchangeably in C standard library, and it's pretty confusing. 似乎这两个约定在C标准库中可以互换使用,这非常令人困惑。

See first answer. 请参阅第一个答案。 It covers this well. 它涵盖了这一点。 (+1 to @Sourav) (+1到@Sourav)

Third question is about error codes themselves. 第三个问题是关于错误代码本身。 In C# and Java, there are standard exception types that cover almost everything you would typically throw, apart from specific comments. 在C#和Java中,除了特定的注释外,标准的异常类型几乎涵盖了您通常会抛出的所有内容。 Should I use errno error code table for this purpose, or should I create a separate error code table for my application to include all stuff that it specific to it? 我应该为此目的使用errno错误代码表,还是应该为我的应用程序创建一个单独的错误代码表以包含所有特定于它的东西? Or may be every function should maintain it's own specific table of error codes? 还是每个函数都应该维护自己的特定错误代码表?

This is completely up to the developer . 这完全取决于开发人员 When creating an application, I will use specific error codes/messages native to the library call that generated the condition. 创建应用程序时,我将使用生成条件的库调用固有的特定错误代码/消息。 When I create an API (usually .dll) I will often create a single enum containing all possible return conditions specific to the application, and will integrate C native library errors into this enum as well. 当我创建一个API(通常是.dll)时,我通常会创建一个包含该应用程序所有可能的返回条件的枚举,并将C本机库错误也集成到该枚举中。 Zero/Positive values for success conditions, and negative for error conditions. 成功条件为零/正值,错误条件为负。 Along with that I create an array of string descriptions corresponding to each return condition. 同时,我创建了一个与每个返回条件相对应的字符串描述数组。 These are callable by the application via a function, eg. 这些可以由应用程序通过一个函数调用,例如 int GetErrorDescription(int error, char *str); . However, this can be approached several different ways, this is just my approach. 但是,可以用几种不同的方法来处理,这只是我的方法。

Example: 例:

/*------------------------------------------
//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
};

And then a function to get the description: 然后一个函数来获取的描述:

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;
}

And finally, additional information about errors: I found that in C#, at least, it's very useful to be able to throw an exception of predefined type with a specific comment that provides additional information. 最后,关于错误的其他信息:我发现,至少在C#中,能够抛出预定义类型的异常并带有提供更多信息的特定注释非常有用。 Is something like this used in C error management? C错误管理中使用了类似的东西吗? How should I implement it? 我应该如何实施?

comment addressing previous question also addresses this. 解决上一个问题的评论也解决了这个问题

Why do library functions use return codes and errno at the same time instead of sticking to one or the other? 为什么库函数同时使用返回码和errno而不是一个或另一个? What's the benefit in using both? 同时使用两者有什么好处?

again , covered by Sourav. 再次 ,由苏拉夫覆盖。

Generally speaking, exception-type behavior isn't done in C. The convention is that the return value of each function is expected to be checked and handled by the immediate caller, along with any ancillary variable (such as errno) that the function may set. 一般而言,异常类型的行为不是用C语言完成的。约定是,每个函数的返回值都应由直接调用者以及该函数可能附带的任何辅助变量(例如errno)进行检查和处理。组。

That being said, you could do something similar to exceptions by using setjmp() and longjmp() . 话虽如此,您可以通过使用setjmp()longjmp()来执行与异常相似的操作。 You use setjmp() to set a jump point, passing in a jmp_buf structure to save the jump point. 您可以使用setjmp()设置跳转点,并传入jmp_buf结构以保存跳转点。 Later on in the code, no matter how far down the call stack, you can pass that jmpbuf to longjmp() which will result in control going back up to where setjmp() was called. 在代码的后面,无论调用堆栈有多远,您都可以将该jmpbuf传递给longjmp() ,这将导致控制权返回到调用setjmp()位置。 You also pass a value to longjmp() which will be the return value of setjmp() . 您还将一个值传递给longjmp() ,它将是setjmp()的返回值。 You can then use this value as a lookup into an error table to print a more detailed message. 然后,您可以将该值用作对错误表的查找,以打印更详细的消息。

There are several other questions related to longjmp() that you can read up on. 您还可以阅读与longjmp()相关的其他几个问题。

EDIT: 编辑:

An example: 一个例子:

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));
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM