带管道的多进程程序中的意外 scanf 行为

unexpected scanf behavior in multi-process program with pipes

作为 UNIX 中的一个练习 OS class 我应该开发一个程序,其中包含两个通过管道进行通信的同级进程。一个进程(生产者)应该从 stdout 读取字符串并将它们发送到另一个进程(消费者),然后该进程必须将字符串转换为大写并将其再次打印到 stdout。字符串“end”终止了兄弟进程和父进程。

该程序运行良好,但在尝试从用户那里读取整个短语而不是单个字符串时出现故障。

这是我主要的一部分:

int main(void) {
int child_status, fd[2];

if (pipe(fd)) {
  fprintf(stderr, "Error: Could not create pipe.\n");
  return EXIT_FAILURE;
}

if (!fork()) {
  // child1
  producer(fd);
  exit(0);
} else if (!fork()) {
  // child2
  consumer(fd); 
  exit(0);
} else {
  // father
  pid_t pid;
...

生产者进程:

void producer(int *fd) {
   char buff[BUFFSIZE];
   char *line;
 
   close(fd[0]);
   while (1) {
     fflush(stdout);
     fprintf(stdout, "Insert string:\t");
     scanf("%[^\n]s%*c", buff);
     fflush(stdout);
     line = strdup(buff);
     printToPipe(fd[1], line);
 
     if (!strcmp(line, "end")) // Special string terminates process
       return;
 
     sleep(1);
   }
 
   return;
 }

以及消费者进程:

 void consumer(int *fd) {
   char *buff;
 
   close(fd[1]);
   while (1) {
     if ( (buff = readFromPipe(fd[0])) == NULL ) {
       close(fd[0]);
       return;
     }
     fprintf(stdout, "%s\n", strToUpper(buff));
     fflush(stdout);
   }
 
   return;
 }

printToPipe() 和 readfromPipe() 直接实现了 read() 和 write() 系统调用。

意外行为:

一旦输入第一个短语并将其转换为大写,程序就会进入无限循环,打印出提示和第一个大写短语(即:Insert string: TEST TEST)。特别是:

subsequent scanfs are ignored despite me accounting for the \n character

代码从不读取 '\n'

scanf("%[^\n]s%*c", buff);是个问题.

  1. "%[^\n]" 将无限数量的非 '\n 字符读入 buff,可能溢出 buff。比gets()还差。如果至少读取了 1 个非 '\n' 字符,则将 空字符 附加到 buff,否则扫描停止。

  2. "s" 匹配一个 's',这在上述之后不是预期的 - 只有一个 '\n',所以扫描停止而不消耗任何 '\n'

  3. 格式的其余部分永远不会执行。

代码从不检查输入函数的 return 值

通过检查scanf()的return值,buff的内容可能不变或不确定。

// scanf("%[^\n]s%*c", buff);
if (scanf("%[^\n]s%*c", buff) == 1) {
  ; // OK to use `buff`
}

OP 的 scanf() 可能会占用第一行的大部分内容,将 '\n' 留在 stdin 中,但是第二个 scanf() 调用肯定会在尝试时立即停止阅读第一行的 '\n' 。这可能会使 buff 保持不变。

使用 fgets()stdin 读取 的输入并保存为 字符串

// scanf("%[^\n]s%*c", buff);
if (fgets(buff, sizeof buff, stdin) == NULL) {
  ; // Handle end-of-file or rare input error
  break;
}

要从输入行中删除 '\n',请参阅此 answer 和其他内容。
代码可以在字符串中保留 '\n' 并且在打印时不附加一个。

//fprintf(stdout, "%s\n", strToUpper(buff));
fprintf(stdout, "%s", strToUpper(buff));

这将更好地处理超过 BUFFSIZE 的长输入行,因为多余的字符只是留给下一次迭代。

OP 可能需要调整 strcmp(line, "end") 测试以考虑可能的尾随 '\n'


One process (producer) is supposed to read strings from stdout ....

详细信息:生产者不是从 stdout 读取 字符串 ,而是从 stdin 读取 进入 string.