为什么从数据集的 /dev 源成功读取 errno?
Why does successfully reading from a /dev source of data set 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 (Darwin) 上。我在 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
?
errno
不是特定操作的结果,而是构建高级 FILE *stdin
stdio 结构时支持操作的结果。
Libc 通常调用 stat()
,少数其他调用和 lseek()
系统调用失败,因为 /dev/
中的特定文件不是常规文件。它可能取决于特定的 Libc 实现,因此您在各种系统上看到的差异。
但这并不意味着您的操作失败,您应该仅在返回错误代码时检查 errno。
在 Linux 上,您可以使用命令 strace
获取您的程序正在调用的所有系统调用的列表以及导致错误号的确切调用:
strace ./my program
其他系统上也有类似的工具。
我不会说这是 预期的 行为(事实上,我无法在手边的系统上重现它),但 C 标准都允许这样做Posix.
C标准和Posix一般都禁止库函数将errno
设置为0,所以无法清除errno
表示成功。但是,C 允许 errno
被库函数修改“无论是否有错误,前提是 errno
的使用没有记录在
函数的描述”(§7.5 第 3 段)。C 标准没有记录 fgetc
对 errno
的使用。
Posix 更加灵活:它指定
The setting of errno after a successful call to a function is unspecified unless the description of that function specifies that errno shall not be modified.
它确实记录了 fgetc
对 errno
的使用,但该文档并未表明 errno
不应被成功调用修改,只是表明它应该被修改由于读取错误。
具体来说,Posix说的是
If a read error occurs, the error indicator for the stream shall be set, fgetc()
shall return EOF
, and shall set errno
to indicate the error.
换句话说,EOF
可能表示 调用成功 (因为文件结尾不是错误)或者它可能表示错误。只有在后一种情况下 errno
才设置为有意义的值。因此,在检查 errno
之前,您需要验证 ferror(stdin)
是否报告错误。
因此,正如我所说,该行为是标准允许的。为什么 fgetc
实现会那样做?我的猜测是您系统上的 libc
实现在第一次调用时进行了某种惰性初始化。
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 (Darwin) 上。我在 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
?
errno
不是特定操作的结果,而是构建高级 FILE *stdin
stdio 结构时支持操作的结果。
Libc 通常调用 stat()
,少数其他调用和 lseek()
系统调用失败,因为 /dev/
中的特定文件不是常规文件。它可能取决于特定的 Libc 实现,因此您在各种系统上看到的差异。
但这并不意味着您的操作失败,您应该仅在返回错误代码时检查 errno。
在 Linux 上,您可以使用命令 strace
获取您的程序正在调用的所有系统调用的列表以及导致错误号的确切调用:
strace ./my program
其他系统上也有类似的工具。
我不会说这是 预期的 行为(事实上,我无法在手边的系统上重现它),但 C 标准都允许这样做Posix.
C标准和Posix一般都禁止库函数将errno
设置为0,所以无法清除errno
表示成功。但是,C 允许 errno
被库函数修改“无论是否有错误,前提是 errno
的使用没有记录在
函数的描述”(§7.5 第 3 段)。C 标准没有记录 fgetc
对 errno
的使用。
Posix 更加灵活:它指定
The setting of errno after a successful call to a function is unspecified unless the description of that function specifies that errno shall not be modified.
它确实记录了 fgetc
对 errno
的使用,但该文档并未表明 errno
不应被成功调用修改,只是表明它应该被修改由于读取错误。
具体来说,Posix说的是
If a read error occurs, the error indicator for the stream shall be set,
fgetc()
shall returnEOF
, and shall seterrno
to indicate the error.
换句话说,EOF
可能表示 调用成功 (因为文件结尾不是错误)或者它可能表示错误。只有在后一种情况下 errno
才设置为有意义的值。因此,在检查 errno
之前,您需要验证 ferror(stdin)
是否报告错误。
因此,正如我所说,该行为是标准允许的。为什么 fgetc
实现会那样做?我的猜测是您系统上的 libc
实现在第一次调用时进行了某种惰性初始化。