标准说:
perror()函数不应更改标准错误流的方向。
这是perror()在GNU中的实现。
下面是在调用perror()之前,stderr是面向宽的、面向多字节的和不面向的测试。测试1)和测试2)都是正常的。问题在测试3中)。
1) stderr是面向广泛的:
#include <stdio.h>
#include <wchar.h>
#include <errno.h>
int main(void)
{
fwide(stderr, 1);
errno = EINVAL;
perror("");
int x = fwide(stderr, 0);
printf("fwide: %d\n",x);
return 0;
}$ ./a.out
Invalid argument
fwide: 1
$ ./a.out 2>/dev/null
fwide: 12) stderr是面向多字节的:
#include <stdio.h>
#include <wchar.h>
#include <errno.h>
int main(void)
{
fwide(stderr, -1);
errno = EINVAL;
perror("");
int x = fwide(stderr, 0);
printf("fwide: %d\n",x);
return 0;
}$ ./a.out
Invalid argument
fwide: -1
$ ./a.out 2>/dev/null
fwide: -13) stderr不面向:
#include <stdio.h>
#include <wchar.h>
#include <errno.h>
int main(void)
{
printf("initial fwide: %d\n", fwide(stderr, 0));
errno = EINVAL;
perror("");
int x = fwide(stderr, 0);
printf("fwide: %d\n", x);
return 0;
}$ ./a.out
initial fwide: 0
Invalid argument
fwide: 0
$ ./a.out 2>/dev/null
initial fwide: 0
fwide: -1如果perror()被重定向,为什么它会改变流的方向?行为得体吗?
这代码是如何工作的?__dup的把戏到底是什么?
发布于 2016-10-10 05:00:09
医生:是的,这是个灵长类中的小虫子。如果你关心这件事,你应该报告。
引用的关于perror不改变流方向的要求在Posix中,但似乎并不是C标准本身所要求的。然而,Posix似乎非常坚持stderr的方向不被perror改变,即使stderr还没有面向。XSH 2.5标准I/O流:
如果流已经面向字节,perror()、psiginfo()和psiginfo()函数应该像上面描述的那样运行,如果流已经是面向字节的,则应该对宽字符输出函数进行上述操作。如果流没有方向,则它们的行为应与字节输出函数的描述相同,除非它们不应更改流的方向。
而glibc试图实现Posix语义。不幸的是,它并不完全正确。
当然,不设置流的方向是不可能写入流的。因此,为了满足这一奇怪的要求,glibc尝试使用OP末尾指向的代码,基于与stderr相同的fd创建一个新流:
58 if (__builtin_expect (_IO_fwide (stderr, 0) != 0, 1)
59 || (fd = __fileno (stderr)) == -1
60 || (fd = __dup (fd)) == -1
61 || (fp = fdopen (fd, "w+")) == NULL)
62 { ...除去内部符号,基本上相当于:
if (fwide (stderr, 0) != 0
|| (fd = fileno (stderr)) == -1
|| (fd = dup (fd)) == -1
|| (fp = fdopen (fd, "w+")) == NULL)
{
/* Either stderr has an orientation or the duplication failed,
* so just write to stderr
*/
if (fd != -1) close(fd);
perror_internal(stderr, s, errnum);
}
else
{
/* Write the message to fp instead of stderr */
perror_internal(fp, s, errnum);
fclose(fp);
}fileno从标准C库流中提取fd。dup获取一个fd,复制它,并返回副本的数量。fdopen从fd创建一个标准的C库流。简而言之,这不会重新打开stderr;相反,它会创建(或试图创建) stderr的副本,该副本可以被写入,而不会影响stderr的方向。
不幸的是,由于模式的原因,它不能可靠地工作:
fp = fdopen(fd, "w+");它试图打开一个允许读写的流。它将与原来的stderr一起工作,它只是控制台fd的一个副本,最初是为读写打开的。但是,当您用重定向将stderr绑定到其他设备时:
$ ./a.out 2>/dev/null您正在传递只为输出打开的fd可执行文件。fdopen不会让你逃脱的:
应用程序应确保模式参数所表示的流模式被fildes引用的打开文件描述的文件访问模式所允许。
fdopen的glibc实现实际上会检查,如果您指定了一种需要对fd不可用的访问权限的模式,则返回NULL,并将errno设置为EINVAL。
因此,如果您将stderr重定向为读和写,您就可以通过测试:
$ ./a.out 2<>/dev/null但是,首先您可能想要的是在附加模式下重定向stderr:
$ ./a.out 2>>/dev/null据我所知,bash没有提供读取/追加重定向的方法。
我不知道为什么glibc代码使用"w+"作为模式参数,因为它没有从stderr读取的意图。"w"应该可以正常工作,尽管它可能不会保留附加模式,这可能会带来不幸的后果。
发布于 2016-10-10 03:33:28
我不确定是否有一个很好的答案“为什么”,而不问滑头的开发人员-这可能只是一个bug -但POSIX的要求似乎与国际标准化组织的要求,其中阅读7.21.2,4
每条溪流都有一个方向。在流与外部文件相关联之后,但是在对其执行任何操作之前,流是没有方向的。一旦将宽字符输入/输出函数应用到没有定向的流中,该流就变成了面向宽方向的流。类似地,一旦一个字节输入/输出函数被应用到一个没有定向的流中,该流就变成了一个面向字节的流。只有对freopen函数或fwide函数的调用才能改变流的方向。(对freopen的成功调用消除了任何方向。)
此外,perror似乎被限定为一个“字节I/O函数”,因为它需要一个char *,并且,按照7.21.10.4 2,它“写一个字符序列”。
由于POSIX在发生冲突时服从ISO C,因此有一个论点认为这里的POSIX要求是无效的。
至于问题中的实际例子:
perror是正确的,并且不会因为调用而改变。perror将流定向为面向字节。这似乎是ISO C的要求,但POSIX不允许。https://stackoverflow.com/questions/39950678
复制相似问题