管道上的EOF打印垃圾信息

4

编辑: 经过一些额外的调试,EOF 已经成功地被写入管道(我知道这是因为我测试了 write() 函数在 produceStdin 上是否返回 0)。然而,当从同一个管道中读取时,它说我遇到了 EOF(好的),但 EOF 元素的值等于 255(而不是像通常情况下的 -1)。有人知道这是为什么吗?

我正在尝试编写这个程序,但是当我从 stdin 中遇到 EOF 时,它没有将 -1 写入管道。由于尝试通过管道传递 EOF 时会写入垃圾数据,因此所有后续进程都被困在无限循环中。

除了 printOut() 函数中打印数组的语句之外,所有那些打印语句都是我尝试进行调试的(由于 fork 的原因无法使用调试器)。

另外:其中一些注释是回收利用的,所以如果你看到提到“缓冲区”的话,那是因为先前使用的是缓冲区而不是管道。

以下是代码:

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

#define MAX_CHARS 81 //80 chars + null-terminator
#define NUM_CHILDREN 3

void produceStdin(int writePipe);
void child1(int readPipe, int writePipe);
void child2(int readPipe, int writePipe);
void printOut(int readPipe);

int main(int argc, char const *argv[])
{
    int i,pipe1[2],pipe2[2],pipe3[2];
    pid_t childPid;

    if(pipe(pipe1)==-1||pipe(pipe2)==-1||pipe(pipe3)==-1)
    {
        fprintf(stderr, "Error in creating pipe");
    }
    //despite what it looks like only four children are being forked,
    // all to the same parent. The children get called to their respective 
    //functions where they get terminated before getting to fork themselves.
    for(i=0;i<NUM_CHILDREN;i++)
    {
        childPid=fork();
        switch (childPid) {
            case -1:
                perror("fork() failed. Aborting.");
                exit(EXIT_FAILURE);

            case 0:
                switch (i) {
                    case 0:
                        close(pipe1[0]); //close pipe1 read (since we're reading from stdin)

                        close(pipe2[0]); //close pipe2
                        close(pipe2[1]);

                        printf("right before calling stdin i=%d\n",i);
                        produceStdin(pipe1[1]); //write to pipe1
                        break;

                    case 1:
                        close(pipe1[1]); //close pipe1 write

                        close(pipe2[0]); //close pipe2 read

                        close(pipe3[0]); //close pipe3
                        close(pipe3[1]);
                        printf("right before calling child1 i=%d\n",i);
                        child1(pipe1[0], pipe2[1]); //read from pipe1, write to pipe2
                        break;

                    case 2:
                        close(pipe1[0]); //close pipe1
                        close(pipe1[1]);

                        close(pipe2[1]); //close pipe2 write

                        close(pipe3[0]); //close pipe3 read
                        printf("right before calling child2 i=%d\n",i);
                        child2(pipe2[0], pipe3[1]); //read from pipe2, write to pipe3
                        break;

                    default:
                        break;
                }

            default:
                if(i==2)
                {
                    close(pipe1[1]); //close pipe1
                    close(pipe1[0]);

                    close(pipe2[1]); //close pipe2
                    close(pipe2[0]);

                    close(pipe3[1]); //close pipe3 write

                    printOut(pipe3[0]); //read from pipe3 read
                }
                break;
        }
    }
    return 0;
}
void produceStdin(int writePipe)
{
    int c=0;
    while(c!=EOF)
    {
        c=fgetc(stdin);
        write(writePipe, &c, sizeof(char)); //writing EOF here is where the problem starts I believe
    }
    printf("Got EOF in ProdStdin\n");
    printf("EOF has a value of: %d",c);
    exit(0);
}
void child1(int readPipe, int writePipe)
{
    int c=0;
    while(c!=EOF)
    {
        read(readPipe,&c,sizeof(char));
//        printf("Child1 got a char from pipe1: %c\n",c);
        if(c=='\n')
        {
            c=' '; //test for newline
        }
        write(writePipe, &c, sizeof(char));
    }
    exit(0);
}
void child2(int readPipe, int writePipe)
{
    int c=0;
    int c2=0;
    while(c!=EOF && c2!=EOF)
    {
        read(readPipe, &c, sizeof(char));
//        printf("Child2 got a char from pipe2: %c\n",c);
        if(c=='*')
        {
            read(readPipe, &c2, sizeof(char)); //if c is a * remove another char
            if(c2=='*')
            {
                c='^'; //if c2 is a * then put a ^ on buffer3
                write(writePipe,&c,sizeof(char));
            }
            else
            {
                write(writePipe,&c,sizeof(char));
                write(writePipe,&c2,sizeof(char));
            }
        }
        else
        {
            write(writePipe,&c,sizeof(char));
        }
    }
    exit(0);
}
void printOut(int readPipe)
{
    int c=0,numChars=0;
    char output[MAX_CHARS];
    while (c!=EOF)
    {
        read(readPipe, &c, sizeof(char));
//        printf("PrintOut got a char from pipe3: %c\nnumChars= %d\n",c,numChars);
        if (numChars==MAX_CHARS-2)
        {
            printf("%s\n",output);
            memset(output, '\0', sizeof(char)*MAX_CHARS);
            numChars=0;
        }

        output[numChars]=c;
        numChars++;
    }
    printf("ABOUT TO EXIT PRINTOUT()\n");
    exit(0);
}
2个回答

2

替代方案:通过管道传递读取到的2字节版本,以便接收端可以区分字符和EOF。

int c = 0;
while(c!=EOF) {
  c = fgetc(stdin);
  short sc = (short) c;
  // `sc` will _typically_ have the values -1 (EOF) and 0,1,2,... 255.
  write(writePipe, &sc, sizeof(sc));
}

int c=0;
int c2=0;
while(c != EOF && c2 != EOF) {
  short sc;          
  if (sizeof(sc) != read(readPipe, &sc, sizeof(sc))) handle_error();
  // `sc` will _typically_ have the values -1 (EOF) and 0,1,2,... 255.
  c = sc;
  ...

建议修改后的答案

只有在读取完所有字符之后,c 才会变成 EOF。
建议使用:

// while(c!=EOF) {
//   c=fgetc(stdin);
//   write(writePipe, &c, sizeof(char));
// }
while((c = fgetc(stdin)) != EOF) {
  write(writePipe, &c, sizeof(char));
}
< p >应该评估read(readPipe, &c, sizeof(char));的返回值,而不是寻找c是否成为EOF。EOF不适用于char。< /p>
// int c=0;
// int c2=0;
// while(c!=EOF && c2!=EOF) {
//    read(readPipe, &c, sizeof(char));

char c=0;
char c2=0;
while(1 == read(readPipe, &c, sizeof(char))) {


你能否澄清一下?按照我的循环写法,在退出之前,EOF会被写入到管道中。无论读取的字符是不是EOF,我都想在退出之前将其写入管道,因此我的循环是while(c!=EOF)而不是while((c=fgetc(stdin))!=EOF) - user2494770
换句话说,EOF 已经成功被读取并写入管道中,阅读我在帖子顶部添加的编辑可能会很有用。 - user2494770
好的,那么我可以根据读取重新格式化循环。假设出于固执的原因,我想要写入 EOF 到管道中然后再读取它,这样可以吗?另外,如果我没记错的话,EOF=-1,应该适合一个字节,不是吗?我之所以将我的 c,c2 设为 int 类型而不是 char 是为了容纳 EOF 情况,这个想法是否正确? - user2494770
@sreya "在退出之前,EOF会被写入管道"。这并不正确,因为仅通过传递1个字节,您已经失去了EOF和字符(如\377)之间的区别。 - chux - Reinstate Monica
@sreya,fgetc()返回256个正值和1个负值。要通过管道传递这些信息,应该每次写入使用> 1个字节。替代方法是发布2个写入请求。EOF始终<0。通常为-1,但未指定为此。 - chux - Reinstate Monica
显示剩余2条评论

0

在编写代码时,这一部分存在问题:

int c = 0;

while(c!=EOF)
{
    c=fgetc(stdin);
    write(writePipe, &c, sizeof(char));
}

你应该使用:

int c;

while ((c = fgetc(stdin)) != EOF)
{
    char c1 = c;
    write(writePipe, &c1, sizeof(char));
}

fgetc() 的输出要么是作为 int 返回的无符号字符值,要么是 EOF。你检测到了 EOF,然后写入了一个不应该被写入的字节到管道中。在一般情况下,使用 c1 是必要的;你可能可以在小端机器上运行你编写的代码,但在大端机器上它将无法正常工作。你还应该检查写操作是否成功。

等效的读取代码也存在类似的问题。你有:

int c=0;
while(c!=EOF)
{
    read(readPipe,&c,sizeof(char));
    if(c=='\n')
    {
        c=' '; //test for newline
    }
    write(writePipe, &c, sizeof(char));
}

你需要:

char c;
while (read(readPipe, &c, sizeof(char)) == sizeof(char))
{
    if (c == '\n')
        c = ' ';
    write(writePipe, &c, sizeof(char));
}

再次,有小端机与大端机可移植性问题,以及输入操作的测试(总是,但永远要检查输入操作;你通常可以不检查输出,但必须始终检查输入)。

请注意,针对高性能应用,通过read()write()进行单字符I/O是昂贵的。标准I/O单字符I/O是可以接受的,因为它缓冲输入或输出,并且不会调用每次读取和写入的系统调用开销。对于玩具应用程序,你不会注意到这些开销。

这可能就是chux在他的answer中所说的,用稍微不同的方式表达出来。


是的,这只是一个概念性的练习,而不是一个性能练习,但感谢您的建议。如果您无法实际传递EOF,那么如何通过管道传递消息以让每个进程知道输入流已经到达EOF?只需传递自定义变量或其他内容即可。 - user2494770
就像我完成写作后关闭了管道,这似乎已经完成了工作。 - user2494770
是的;关闭管道会让另一端知道没有更多的数据了——只要有一个进程打开了管道的写端。因此,在一组进程中,确保关闭所有未使用的管道描述符非常重要。这通常需要很多次关闭操作。把“管道”看作“通过关闭所有不需要的管道来停止(文件描述符的)泄漏”。 - Jonathan Leffler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接