C库函数的原理从不将errno设置为零


9

C标准要求不得将C标准库功能设置errno为零。为什么会这样呢?

我可以理解它对于调用多个函数以及仅errno在最后一个函数之后进行检查很有用-例如:

errno = 0;
double x = strtod(str1, NULL);
long y = strtol(str2, NULL);
if (errno)
    // either "strtod" or "strtol" failed
else
    // both succeeded

但是,这不是“坏习惯”吗?由于只errno在最后进行检查,因此您只知道其中一个功能确实失败了,但哪个功能失败了。仅仅是知道某些失败对于大多数实际程序而言已经足够好了吗?

我尝试查找各种​​C Rational文档,但是其中许多文档没有太多详细信息<errno.h>


1
我猜这是“不为您不想要的东西付钱”。如果您关心errno,可以随时将其设置为零。
Kerrek SB 2013年

2
还有一点需要注意的是,errno即使函数成功,也可以将其设置为非零值。(它可能会调用其他一些失败的函数,但这并不构成外部函数的失败。)
Keith Thompson

Answers:


10

由于errno历史原因1,C库未设置为0 。POSIX不再声称它的库在成功的情况下不会改变其值,新的Linux手册页errno.h反映了这一点:

<errno.h>头文件定义了整型变量errno,它是由系统调用,并在出现错误的情况下,一些库函数来表示什么地方出了错设置。仅当调用的返回值指示错误时(即,-1来自大多数系统调用;-1NULL来自大多数库函数),其值才有意义。这成功的功能允许改变errno

ANSI C的理由指出,委员会认为这是更实际的采用和规范使用的现行做法errno

errno通常以设置为中心的错误报告机制充其量是可以容忍的。它需要库函数之间的``病理耦合'',并利用静态可写存储单元,这会干扰可共享库的构建。但是,委员会宁愿标准化这种现有的但不足的机制,而不是发明更雄心勃勃的东西。

除了检查是否已errno设置以外,几乎总有一种检查错误的方法。errno由于某些调用需要调用单独的API才能获取错误原因,因此检查设置是否总是不可靠。例如,ferror()如果您从fread()或得到短结果,则用于检查错误fwrite()

有趣的是,您的使用示例strtod()errno要求正确地检测是否发生错误的调用之前将其设置为0 的情况之一。所有的strto*()字符串到数字函数都具有此要求,因为即使遇到错误也会返回有效的返回值。

errno = 0;
char *endptr;
double x = strtod(str1, &endptr);
if (endptr == str1) {
    /*...parse error */
} else if (errno == ERANGE) {
    if (x == 0) {
        /*...underflow */
    } else if (x == HUGE_VAL) {
        /*...positive overflow */
    } else if (x == -HUGE_VAL) {
        /*...negative overflow */
    } else {
        /*...unknown range error? */
    }
}

上面的代码基于strtod()Linux上记录的行为。C标准仅规定下溢不能返回大于最小正数的值double,并且是否将errno其设置ERANGE为实现定义2

实际上,有大量的证书咨询文章,建议始终errno在调用库之前将其设置为0,并在调用指示发生故障后检查其值。这是因为errno即使调用本身成功,也会设置一些库调用3

errno程序启动时的值为0,但是任何库函数都不会将其设置为0。errno可以通过库函数调用将是否存在错误设置为非零值,前提errno是在C标准的函数说明中未记录对的使用。对于程序来说,errno仅在报告错误后才检查其内容是有意义的。更准确地说,errno只有在设置errno了错误的库函数返回错误代码后,该变量才有意义。


1.以前我声称这是为了避免掩盖先前调用中的错误。我找不到任何证据支持这一说法。我也有一个伪造的printf()例子。
2.感谢@chux指出这一点。参考是C.11§7.22.1.3¶10。
3. @KeithThompson在评论中指出。


较小的问题:下溢可能导致非零结果:“函数返回的值的大小不大于返回类型中的最小归一化正数” C11 7.22.1.3.10。
chux-恢复莫妮卡

@chux:谢谢。我将进行编辑。由于设置errnoERANGE下溢的情况下实现定义,实际上是没有检测到下溢移植的方法。我的代码遵循在系统的Linux手册页中找到的代码。
jxh

您对strto*函数的评论正是我问我的示例是否被视为不良做法的原因,但这是errno不设置为零甚至有用的唯一方法。

@DrewMcGowen:我想您唯一可以做的就是errno即使成功也可以设置,因此仅使用设置好的事实并不能很好地指示发生错误。您必须检查各个呼叫本身的结果,以了解是否发生了错误。
2013年

1

如果您真的很在意,您可以对两个函数都进行错误检查。

errno = 0;
double x = strtod(str1, NULL);
if (errno)
    // strtod"  failed
else
    // "strtod" succeeded

long y = strtol(str2, NULL);
if (errno)
    // "strtol" failed
else
    // "strtol" succeeded

由于我们永远不知道进程中如何调用马赫函数,因此lib如何为每个函数调用设置errnos,对吗?


1

任意更改errorno类似于“捕获并吞并”异常。在存在异常会传播到程序的不同层并最终到达调用者将以某种方式捕获并响应异常或简单地传递责任之前,存在错误。除非您以某种方式处理,覆盖,将错误视为无关紧要的错误,否则不要更改错误号,这对于此第一代错误处理传播模式至关重要。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.