[英]What is the correct way to implement error propagation in C?
我来自具有异常(特别是C#)的高级语言,并且通常只捕获我的逻辑知道如何处理的异常-否则,它们会传播并导致程序崩溃,但至少它提供了有关发生的情况的清晰信息,在哪里。 我想在C中实现类似的行为。我想在main
的小部分中,从调用的函数中获取错误代码,以识别发生了什么,并将错误消息打印到stderr
并退出并返回相同的错误我得到的代码。
第一个问题-人们是否使用C语言做到这一点,就架构而言,这是否是一个好的目标?
第二个问题是关于错误返回码和错误号。 默认情况下,我应该在代码中使用什么? 似乎这两个约定在C标准库中可以互换使用,这非常令人困惑。
第三个问题是关于错误代码本身。 在C#和Java中,除了特定的注释外,标准的异常类型几乎涵盖了您通常会抛出的所有内容。 我应该为此目的使用errno错误代码表,还是应该为我的应用程序创建一个单独的错误代码表以包含所有特定于它的东西? 还是每个函数都应该维护自己的特定错误代码表?
最后,关于错误的其他信息:我发现,至少在C#中,能够抛出预定义类型的异常并带有提供更多信息的特定注释非常有用。 C错误管理中使用了类似的东西吗? 我应该如何实施?
为什么库函数同时使用返回码和 errno而不是一个或另一个? 同时使用两者有什么好处?
看来,您可以使用errno
变量和perror()
函数。 在大多数情况下,它们有助于提供失败原因。
为了指示失败,大多数库函数通常会返回带前缀的负值(通常为-1
),并将errno
变量设置为特定值,具体取决于错误的类型。
通常用于此目的吗?
是的,这就是拥有errno
和perror()
的目的,恕我直言。
现在,对于用户定义的函数,您可以为库函数返回的错误创建自己的错误代码(需要一段时间的映射),并且可以从函数中返回自己的值。
编辑:
同时使用两者有什么好处?
在发生故障事件时,以一种简单的方式来区分故障原因。
像这样思考,而不是为错误设置固定的reurn值并设置errno,如果函数针对每种类型的错误返回不同的值,则需要多少个if-else
或switch
语句来检查被调用函数是否成功函数,每次调用? 很大。
而是使用固定的返回值指示错误,如果有错误,则检查errno以了解错误背后的原因。
[希望我很清楚。 英语不是我的母语,对不起]
关于: 我想在我主体的小部分中,从调用的函数中获取错误代码,确定发生了什么,并将错误消息打印到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而不是一个或另一个? 同时使用两者有什么好处?
再次 ,由苏拉夫覆盖。
一般而言,异常类型的行为不是用C语言完成的。约定是,每个函数的返回值都应由直接调用者以及该函数可能附带的任何辅助变量(例如errno)进行检查和处理。组。
话虽如此,您可以通过使用setjmp()
和longjmp()
来执行与异常相似的操作。 您可以使用setjmp()
设置跳转点,并传入jmp_buf
结构以保存跳转点。 在代码的后面,无论调用堆栈有多远,您都可以将该jmpbuf
传递给longjmp()
,这将导致控制权返回到调用setjmp()
位置。 您还将一个值传递给longjmp()
,它将是setjmp()
的返回值。 然后,您可以将该值用作对错误表的查找,以打印更详细的消息。
您还可以阅读与longjmp()
相关的其他几个问题。
编辑:
一个例子:
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.