【问题标题】:Why does successfully reading from a /dev source of data set errno?为什么从 /dev 数据集源成功读取 errno?
【发布时间】:2017-01-03 04:38:30
【问题描述】:

名为get1的C语言简单测试程序:

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main(void) {
    errno = 0;
    int ch = fgetc(stdin);
    printf("ch = %d\n", ch);
    if (errno)
        printf("errno = %d: %s\n", errno, strerror(errno));
    return 0;
}

它只打印以十进制读取的第一个字节,然后如果errno 不为零,则显示errno 和相关的错误消息。

一些结果(foo 是一个文本文件,empty 是一个长度为零的文件):

% ./get1 < foo
ch = 104
% ./get1 < empty
ch = -1

好的,正如预期的那样。然而:

% ./get1 < /dev/zero 
ch = 0
errno = 25: Inappropriate ioctl for device
% ./get1 < /dev/null 
ch = -1
errno = 25: Inappropriate ioctl for device
% ./get1 < /dev/random 
ch = 196
errno = 25: Inappropriate ioctl for device

读取工作正常,但是当我从这些设备中读取时,它设置为errno。为什么?

那是在 macOS(达尔文)上。我在 Linux (Debian) 和 NetBSD 上得到了同样的结果,除了 /dev/random 的不同错误(其他设备上的错误与 macOS 上的相同):

% ./get1 < /dev/random 
ch = 170
errno = 22: Invalid argument

我的理解是,如果你没有得到EOF,那么就没有错误。如果你得到EOF,那么你检查errno 看看是否有错误。在所有这些情况下,从设备获取字节似乎都没有错误,也不应该有。然而errno 已设置。

(删除printf 不会改变任何东西,以防有人想知道。)

事实证明,errno 在 AIX、SunOS 或 OpenSUSE 上从此类设备读取时未设置。

那么这里的交易是什么?这是一个错误,尽管很普遍?这是可接受的行为吗?这是预期的行为吗?为什么设置errno

【问题讨论】:

    标签: c linux macos stdio errno


    【解决方案1】:

    errno不是特定操作的结果,而是构建高级FILE *stdinstdio结构时支持操作的结果。

    Libc 通常调用stat(),很少调用其他和lseek() 系统调用失败,因为/dev/ 中的特定文件不是常规文件。它可能取决于特定的 Libc 实现,因此您在不同系统上看到的差异。

    这并不意味着你的操作失败了,你应该只在返回错误代码的情况下检查errno。

    在 Linux 上,您可以使用命令 strace 获取您的程序正在调用的所有系统调用的列表以及导致 errno 的确切调用:

    strace ./my program
    

    其他系统上也有类似的工具。

    【讨论】:

    • 只有在返回错误码的情况下才应该检查errno。这个规则有一些例外:溢出只能在strtol()和朋友通过设置@987654329来检测通话前@到0,通话后将其与0进行比较。
    • @chqrlie 我们不能使用char **endptr 来检测溢出? if *ptr in [0..9] 之类的东西?
    • @Stargateur: 一个数字可以溢出而不需要太多数字。
    • @rici 没关系,strtol() 即使溢出也会解析所有数字,因为没有无效字符。
    • 如果我执行getchar(),那么我errno设置。所以绝对是getchar() 最终导致errno 被设置,而不管导致该事件的一系列不幸事件的细节如何。
    【解决方案2】:

    我不会说这是预期的行为(事实上,我无法在我方便的系统上重现它),但它是 C 标准和 Posix 允许的。

    C 标准和 Posix 通常都禁止库函数将 errno 设置为 0,因此它们无法清除 errno 以表示成功。但是,C 允许 errno 被库函数修改,“无论是否有错误,只要 errno 的使用没有记录在 函数描述”(第 7.5 节第 3 段)。C 标准没有记录 fgetcerrno 的使用。

    Posix 更加灵活:它指定

    函数调用成功后errno的设置是未指定的,除非该函数的描述指定不应该修改errno。

    它确实记录了fgetcerrno 的使用,但该文档并未表明errno 不应被成功的调用修改,只是它应该被读取错误修改。

    具体来说,Posix 所说的是

    如果发生读取错误,则应设置流的错误指示符,fgetc() 应返回EOF,并应设置errno 以指示错误。

    换句话说,EOF 可能表示调用成功(因为文件结尾不是错误)或者它可能表示错误。只有在后一种情况下,errno 才设置为有意义的值。因此,在检查errno 之前,您需要验证ferror(stdin) 是否报告了错误。

    所以,正如我所说,这种行为是标准允许的。为什么fgetc 实现会这样做?我的猜测是,您系统上的 libc 实现在第一次调用时会进行某种延迟初始化。

    【讨论】:

    • 不正确。 Posix 标准说getchar() 等价于fgetc()fgetc() does document the use of errno, specifically that it shall set errno if there is a read error。只是为了迂腐,我将其更改为 fgetc() 并得到相同的行为。
    • 其实,由于它的行为相同,我将问题中的源代码更改为使用fgetc()
    • @MarkAdler:是的,它将文档推迟到fgetc,但它仍然记录了它。但是文档并没有说errno 在成功时不会被修改。它的意思是,如果发生错误,将设置流的错误错误指示符,而正是这一事实——而不是 EOF 的返回——应该导致您检查 errno
    • 啊,好的,这就是我的问题的答案。我应该先检查ferror(stdin)。如果我这样做,我会看到没有错误。
    • @MarkAdler:我会将其添加到答案中。
    猜你喜欢
    • 2013-09-02
    • 1970-01-01
    • 1970-01-01
    • 2011-12-15
    • 1970-01-01
    • 1970-01-01
    • 2019-11-03
    • 2018-07-31
    • 1970-01-01
    相关资源
    最近更新 更多