【问题标题】:unexpected scanf behavior in multi-process program with pipes带有管道的多进程程序中的意外 scanf 行为
【发布时间】:2021-02-21 14:05:23
【问题描述】:

对于 UNIX OS 课程中的练习,我应该开发一个程序,其中两个同级进程通过管道进行通信。一个进程(生产者)应该从标准输出读取字符串并将它们发送到另一个进程(消费者),然后必须将字符串转换为大写并再次将其打印到标准输出。字符串“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)。特别是:

  • 尽管我考虑了 \n 字符 (scanf("%[^\n]s%*c", buff);),但后续的 scanfs 将被忽略。如果我检查 scanf 返回值,则第一次之后的所有迭代都为 0,
  • 似乎消费者通过其输出“自我维持”自身:它不等待通过管道的新输入并继续打印转换后的短语。

【问题讨论】:

  • "%[^\n]s%*c" --> "%[^\n]%*c"[...] 本身就是一个字段指令,而不是 s 指令的修饰符。您格式中的 s 尝试匹配文字 's' 字符。
  • 它不能解决您的问题,那么我们可能需要查看minimal reproducible example。请注意,在这种情况下,准备一个将部分涉及将问题缩小到初始输入或通过管道传输数据
  • 关于:scanf("%[^\n]s%*c", buff); 输入格式说明符:%[\n] 将在输入流中保留 '\n'。所以下一个字符永远不会是s 建议:scanf("%[^\n]%*c", buff);
  • printToPipereadFromPipe的定义在哪里???

标签: c loops process pipe scanf


【解决方案1】:

尽管我考虑了 \n 字符,但后续的 scanfs 将被忽略

代码从不读取'\n'

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

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

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

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

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

通过检查scanf()的返回值,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 读取 输入并保存为字符串@xing

// 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'


一个进程(生产者)应该从标准输出中读取字符串 ....

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

【讨论】:

  • 最终我通过scanf("%[^\n]%*c);得到了预期的行为,我确实误解了field指令的使用。然后,我用更安全的 fgets 替换了 scanf,正如您和@xing 所建议的那样,这就像一个魅力。非常感谢!
  • @Nepec 注意:scanf("%[^\n]%*c);gets 一样不好,因为它没有输入限制,也没有检查函数返回。最好避免和好你已经转移到fgets()
猜你喜欢
  • 1970-01-01
  • 2020-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-01
  • 1970-01-01
  • 2012-03-21
  • 2020-05-01
相关资源
最近更新 更多