为什么成功读取数据集的/ dev源errno?

时间:2017-01-03 04:38:30

标签: c linux macos stdio errno

C语言中的简单测试程序get1

#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不会改变任何内容,以防有人想知道。)

事实证明,在AIX,SunOS或OpenSUSE上从这样的设备读取时,未设置errno

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

2 个答案:

答案 0 :(得分:4)

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

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

这并不代表您的操作失败,只有在返回错误代码的情况下才应检查错误。

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

strace ./my program

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

答案 1 :(得分:2)

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

C标准和Posix通常禁止库函数将errno设置为0,因此无法清除errno表示成功。但是,C允许errno由库函数修改“是否存在错误,前提是errno的使用没有记录在 函数描述“(§7.5第3段).C标准没有记录errnofgetc的使用。

Posix更灵活:它指定了

  

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

它确实记录了errno fgetc的使用情况,但该文档并未表明成功调用不会修改errno,只是应该通过读错误。

具体来说,Posix所说的是

  

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

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

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