C 库中的错误报告
Posted
技术标签:
【中文标题】C 库中的错误报告【英文标题】:Error reporting in a C library 【发布时间】:2011-09-26 18:50:28 【问题描述】:我正在寻找一种可靠的方法来报告 C 库中的错误。考虑一个简单的队列示例:
struct queue *q = malloc(sizeof(*q));
if (NULL == q)
/* malloc failed. now what ? */
return NULL; /* maybe ? */
好的,因此对于该示例,返回 NULL
在其他情况下无效,因此返回它以表示错误是有意义的。但是
void *get_data()
/* stuff */
/* Error detected. NULL is a valid return, now what ? */
/* stuff */
更重要的是,一旦我们发出错误信号,如何发出 what 是错误信号?我已经考虑过了,没有满意的解决方案。
不能使用errno
或其他一些全局对象
我想做的事情(也许
可以从以下位置调用函数
多个线程等)。
我想过让客户供应 一些“状态”对象可以是 通话后进行了检查,但是 会使 API 变得非常丑陋。
那么你对这个主题有什么看法?如何以干净的方式报告错误?
【问题讨论】:
另一种选择是使返回值具有不同的含义(或只是返回状态)并在参数中传递对象(指针)......例如,system
确实
@pmg 这也是一种明智的做法。
老实说,我即将创建一个错误代码位掩码,并将我的位读取/写入/等的所有错误字段放入它自己的结构中,我每次运行时都会检查它。
【参考方案1】:
int get_data(void **ptr)
如果没有明显的“错误返回”,那么您的输出值可能不应该是返回值。错误可能是一个 errno,一些其他自定义详细错误值 (*cough* HRESULT),如果函数成功,则为 true/false,或者其他一些有用信息(数据长度,如果错误则为 -1 )
【讨论】:
【参考方案2】:将“状态”参数作为指针传递可能看起来有点难看,但在 C 编程中是可以接受的,因为该语言中不存在更高级别的错误报告工具。
status变量中的每一位代表不同的错误,所以如果返回NULL
,调用者将不得不测试是status & ERR_NOMEM
还是status & ERR_IO
等。错误掩码可以定义如下:
#define ERR_NOMEM (1 << 0)
#define ERR_IO (1 << 1)
...
在函数内部设置适当的错误可以通过status |= ERR_IO
来完成。
这甚至使您有时可以灵活地指示多个错误 - 调用者可以检查 status & (ERR_NOMEM | ERR_IO)
以测试是否发生了任何错误。
【讨论】:
如果你还是要传递一个参数,为什么不传递一个指向结构的指针呢?这样你就可以完全改变(即使你想保持二进制兼容性也可以决定)东西是如何工作的,并拥有像这样的接口:mylib_fancy_error_msg(&mylib_state)
。【参考方案3】:
我的第一个想法:为什么不使用 stderr,用指示问题来源和原因的消息填充它?或者,也许我错过了你的意图:)
【讨论】:
1.因为我会污染客户端的stderr
。 2. 因为stderr
甚至可能不可用(GUI 应用程序?) 3. 因为客户端必须解析字符串才能找到错误(我以后将无法在不破坏程序的情况下更改这些字符串)。
好的。我知道你在做什么,但为了回应:) - 1. 如果你包含一些文本前缀,如“MyLib:”,它可以很容易地被过滤。 2. 如果您使用简单的 fprintf,您将能够将输出流更改为您想要的内容。此外,我认为 stderr 的可用性与 GUI 程序无关。 3. 对于您决定的任何错误报告方式,这都是正确的。不管前者,其他解决方案是从具有详细错误描述的函数结构返回。我会这样做,因为它不需要您使用不雅的全局变量。
好吧,(2):也许客户端不想通过 I/O 来获得错误。并且 (3):如果我使用整数代码,客户端将不必解析任何内容。
2.是的......可能:),3。我的意思是,如果不破坏现有的依赖程序,您将无法更改特定的错误“代码”。但是......返回结构怎么样?必须涉及一些内存管理,但恕我直言,这是最灵活的解决方案?【参考方案4】:
我有几个建议。
建议 #1 -- 使用自定义 errnos。 我知道您表示您不想使用它。我了解到您担心 errno 会在多线程环境中被破坏,但我希望每个线程都应该有自己的 errno 存储。以下链接http://compute.cnr.berkeley.edu/cgi-bin/man-cgi?errno+3 表明它很容易做到。
就构建自定义 errno 而言,您始终可以将 errno 分为两部分……模块编号和模块错误代码。
eg.
#define MODULE_NAME_error_code ((MODULE_NUMBER << 16) | (error_code))
如果在您的库中检测到错误,您可以将 errno 设置为所需的值。如果有多个模块,它可以帮助识别问题区域。当然,如果您的库要与使用此方法的其他库一起使用,则需要某种形式的自定义 errno 同步。
建议 #2 -- 让您的例程在成功时返回 0,并在返回时使用自定义的非零值,并让其中一个参数成为您要设置的值的指针。可以很容易地检测到错误;但是,如果您有使用这种方法的深层调用树,那么记录它们可能会很麻烦。
希望这会有所帮助。
【讨论】:
【参考方案5】:如果您真的想要一种多线程、可重入的方式来报告错误,我认为您无法在每个 lib 调用中逃避将指针传递给“状态”结构。该结构将包含其他对象状态内容,即相关调用的结果。
确实很丑,但不一定是坏的。
【讨论】:
您可以通过使用线程本地存储来维护错误结构并让函数仅报告是否发生错误,从而以一种不那么难看的方式实现这一点。因此,例如,void foo(int bar, int biz, struct my_error_t *e);
变为 int foo(int bar, int biz);
,就像普通函数一样,其中,例如,非零返回值表示错误。当发生错误时,foo
调用set_my_error
来修改线程局部错误上下文。同样,foo
的调用者可以调用get_my_error
来检索详细的错误信息,如果需要的话。以上是关于C 库中的错误报告的主要内容,如果未能解决你的问题,请参考以下文章