首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么perror()在重定向时更改流的方向?

为什么perror()在重定向时更改流的方向?
EN

Stack Overflow用户
提问于 2016-10-10 02:50:50
回答 2查看 307关注 0票数 3

标准说:

perror()函数不应更改标准错误流的方向。

perror()在GNU中的实现。

下面是在调用perror()之前,stderr是面向宽的、面向多字节的和不面向的测试。测试1)和测试2)都是正常的。问题在测试3中)。

1) stderr是面向广泛的:

代码语言:javascript
复制
#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;
}
代码语言:javascript
复制
$ ./a.out
Invalid argument
fwide: 1
$ ./a.out 2>/dev/null
fwide: 1

2) stderr是面向多字节的:

代码语言:javascript
复制
#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;
}
代码语言:javascript
复制
$ ./a.out
Invalid argument
fwide: -1
$ ./a.out 2>/dev/null
fwide: -1

3) stderr不面向:

代码语言:javascript
复制
#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;
}
代码语言:javascript
复制
$ ./a.out
initial fwide: 0
Invalid argument
fwide: 0
$ ./a.out 2>/dev/null
initial fwide: 0
fwide: -1

如果perror()被重定向,为什么它会改变流的方向?行为得体吗?

代码是如何工作的?__dup的把戏到底是什么?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 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创建一个新流:

代码语言:javascript
复制
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    { ...

除去内部符号,基本上相当于:

代码语言:javascript
复制
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的方向。

不幸的是,由于模式的原因,它不能可靠地工作:

代码语言:javascript
复制
fp = fdopen(fd, "w+");

它试图打开一个允许读写的流。它将与原来的stderr一起工作,它只是控制台fd的一个副本,最初是为读写打开的。但是,当您用重定向将stderr绑定到其他设备时:

代码语言:javascript
复制
$ ./a.out 2>/dev/null

您正在传递只为输出打开的fd可执行文件。fdopen不会让你逃脱的:

应用程序应确保模式参数所表示的流模式被fildes引用的打开文件描述的文件访问模式所允许。

fdopen的glibc实现实际上会检查,如果您指定了一种需要对fd不可用的访问权限的模式,则返回NULL,并将errno设置为EINVAL

因此,如果您将stderr重定向为读和写,您就可以通过测试:

代码语言:javascript
复制
$ ./a.out 2<>/dev/null

但是,首先您可能想要的是在附加模式下重定向stderr

代码语言:javascript
复制
$ ./a.out 2>>/dev/null

据我所知,bash没有提供读取/追加重定向的方法。

我不知道为什么glibc代码使用"w+"作为模式参数,因为它没有从stderr读取的意图。"w"应该可以正常工作,尽管它可能不会保留附加模式,这可能会带来不幸的后果。

票数 2
EN

Stack Overflow用户

发布于 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要求是无效的。

至于问题中的实际例子:

  1. 未定义的行为。在面向宽的流上调用字节I/O函数。
  2. 一点争议都没有。该方向对于调用perror是正确的,并且不会因为调用而改变。
  3. 调用perror将流定向为面向字节。这似乎是ISO C的要求,但POSIX不允许。
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/39950678

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档