【问题标题】:Unexpected behavior of pipes with scanf()使用 scanf() 的管道的意外行为
【发布时间】:2019-04-22 21:00:21
【问题描述】:

自从我上次用 C 编程以来已经有一段时间了,我在使管道工作时遇到了麻烦。 (为了清楚起见,我在 Windows 7 上使用 Cygwin。)特别是,我需要帮助理解以下示例的行为:

/* test.c */

#include <stdio.h>
#include <unistd.h>


int main() {

    char c;
    //scanf("%c", &c); // this is problematic

    int p[2];
    pipe(p);

    int out = dup(STDOUT_FILENO);

    // from now on, implicitly read from and write on pipe
    dup2(p[0], STDIN_FILENO);
    dup2(p[1], STDOUT_FILENO);

    printf("hello");
    fflush(stdout);

    // restore stdout
    dup2(out, STDOUT_FILENO);
    // should read from pipe and write on stdout
    putchar(getchar());
    putchar(getchar());
    putchar(getchar());
}

如果我调用:

echo abcde | ./test.exe

我得到以下输出:

hel

但是,如果我取消注释 scanf 调用,我会得到:

bcd

我无法解释。这实际上是一个具有fork/exec 结构的更复杂程序的非常简化版本,该结构开始表现得很糟糕。尽管没有循环,但它以某种方式开始在无限循环中产生无限的孩子​​。因此,如果规则允许,我可能需要用更具体的使用案例来扩展这个问题。非常感谢。

【问题讨论】:

  • scanf 在您操作文件描述符之前从标准输入读取一堆数据(可能是 4096 或 8192 字节)。
  • 好的,但这与管道有什么关系?如果您的意思是p[0]“开始”与那一堆数据,我应该如何解决它?
  • 不,p[0] 不以数据开头。数据在 stdio 缓冲区中,第一次调用 getchar 将查看该缓冲区,而无需从管道中读取。
  • 现在很清楚了,谢谢。

标签: c unix pipe cygwin scanf


【解决方案1】:

scanf 等流 I/O 函数通常执行缓冲以提高性能。因此,如果您在标准输入上调用scanf,那么它可能会读取比满足请求所需的字符更多的字符,并且额外的字符将等待、缓冲,以供下一次读取。

换出底层文件描述符不会影响先前缓冲的数据。当您随后再次读取该文件时,您会第一次缓冲数据,直到这些数据用完,然后您才能从新的基础文件中获取新数据。

如果您愿意,可以在对流执行任何 I/O 操作之前通过 setvbuf() 函数关闭流的缓冲:

int result = setvbuf(stdin, NULL, _IONBF, 0);
if (result != 0) {
    // handle error ...
}

这实际上是一个更复杂程序的非常简化的版本 具有开始表现非常糟糕的 fork/exec 结构。尽管没有 有循环,它以某种方式开始在一个 无限循环。

我看不出这种行为与您在此处提出的问题有何关联。

所以,如果规则允许,我可能需要扩展 一个更具体的使用案例的问题。

那将是一个单独的问题。

【讨论】:

  • 谢谢。只是出于好奇,还有其他方法吗?如果我想保留缓冲怎么办?编辑:另外,它也会影响子进程吗?我是说。进程分叉时缓冲区是否被复制?
  • 关于子进程,是的,显然。
  • 是的,@giofrida,缓冲区在进程的内存中,因此被复制到fork()。这本身就是错误的有时来源。
  • 至于另一种方式,我不确定我是否理解:如果您要使用 stdio 功能,那么您必须考虑缓冲(在逐个流的基础上)。您可以在三种缓冲样式之间进行选择,其中一种为“无”。或者在支持fork() 的系统上,您可能还拥有低级别的read()write() 函数。如果你愿意,这些留给你做缓冲。
  • 父母应该逐行阅读stdin,并每次将它们传递给一个新的孩子(是的,会有一个循环,但我无法实现完整的程序) .但是,在子程序executes 上线之前,它必须将其中的一部分重新注入stdin(通过管道),因为这是子程序的工作方式,我无法更改它。无论如何,我选择完全禁用缓冲。编辑:如果有其他方法可以做到这一点,我很想知道。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-05-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多