pipe - 使用 scanf 从管道读取失败
问题描述
在 IPC 上工作时,我被要求编写一个 C 程序,作为其他两个 C 可执行文件之间的管道:
第一个名为“sln1.out”的可执行文件接收六个参数并打印三个数字。
第二个名为“sln2.out”的可执行文件接收三个参数并打印一个数字。
我将以下代码分为两部分——第一部分是写入管道,据我所知它是有效的。问题从第二部分开始:我stdin
现在关闭了所以,当我使用dup(fd[0])
新的文件描述符副本时,应将其分配到stdin
原来的位置,我想scanf
在这种情况下我可以使用从管道中读取 - 但由于某种原因它没有不工作
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
// check number of arguments.
if(argc != 7)
{
printf("Wrong parameters");
exit(1);
}
// creating the pipe.
int fd[2];
pipe(fd);
/* PART ONE: forking a child for 'sln1.out' that writes to fd[1] */
// I want to fork this process, and change the image of the child process to the 'sln1.out' process.
pid_t pid_sln1 = fork();
int sln1_status;
if (pid_sln1 < 0)
{
perror("fork error, sln1");
}
else if(pid_sln1 == 0)
{
char* const parmListSln1[] = {"./sln1.out",argv[1],argv[2],argv[3],
argv[4],argv[5],argv[6],NULL};
// i closed the stdout, and used 'dup' that return the file descriptor
// of stdout as duplicate of fd[1]!
close(STDOUT_FILENO);
dup(fd[1]);
execv("./sln1.out",parmListSln1);
printf("Return not expected, exacv error.\n");
exit(1);
}
// wait untill the child process terminated.
wait(&sln1_status);
if(sln1_status == 0)
{
printf("child process terminated successfully\n");
// if we want to read from fd[0] we must close the write to fd[1]
close(fd[1]);
}
else
{
printf("child process failed\n");
exit(1);
}
/* PART TWO: forking a child for 'sln2.out' that reads from fd[0] */
// The same idea - forking a child to change its image to the 'sln2.out' process.
pid_t pid_sln2 = fork();
int sln2_status;
if(pid_sln2 < 0)
{
printf("fork error, sln2.\n");
exit(1);
}
else if(pid_sln2 == 0)
{
// closing 'stdin' and the use fo 'dup' create a duplicate to the readable
// side of the pipe where the standard input should be
close(STDIN_FILENO);
dup(fd[0]);
// reading the input from the pipe - with the same method used to 'stdin'!
char* in[3];
scanf("%s %s %s",in[0],in[1],in[2]);
// build the parameters list for 'sln2.out'
char* const paramListSln2[] = { "./sln2.out", in[0], in[1], in[2], NULL };
// execute 'sln2.out'
execv("./sln2.out",paramListSln2);
printf("Return not expexted, execv error");
exit(1);
}
// wait untill the child process terminated and determine success.
wait(&sln2_status);
if (sln2_status == 0)
{
printf("2nd child process terminated successfully!\n");
exit(0);
}
else
{
printf("error with 'sln2.out' child process.\n");
exit(1);
}
exit(0);
}
我得到的输出可以提供更多细节:
child process terminated successfully
error with 'sln2.out' child process.
我很确定这个sln2.out
过程的问题是因为scanf
我试图打印扫描的参数并且它也失败了......
解决方案
主要问题——未初始化的指针
ctrl61.c
当我使用命令行编译问题中的代码(源文件, )时:
gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes ctrl61.c -o ctrl61
(在带有 macOS 10.14.2 Mojave 的 Mac 上运行 GCC 8.2.0),我收到如下警告:
ctrl61.c:78:13: error: ‘in[0]’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
对于in[0]
,in[1]
和中的每一个,in[2]
标识的行是对 . 的调用scanf()
。未初始化的指针是崩溃的根源,实际上,检查代码表明指针未初始化。您需要为指向的指针分配存储空间。最简单的更改是使用:
char in[3][50];
(尽管您应该每次都在scanf()
格式字符串中使用 %49s。)或者您可以使用m
修饰符 to%s
和其他相应的更改,以便scanf()
为您分配内存。请注意,某些系统(例如 macOS)不支持 POSIX 强制sscanf()
修饰符。
您没有在子级(或者实际上,在父级)中关闭足够的文件描述符。
经验法则:如果您
将管道的一端连接到标准输入或标准输出,请
尽快dup2()
关闭返回的两个原始文件描述符
。pipe()
特别是,您应该在使用任何
exec*()
函数系列之前关闭它们。
dup()
如果您使用“F_DUPFD”或
“F_DUPFD”复制描述符,该规则也适用
fcntl()
。
在这个程序中,这可能无关紧要,但如果您更普遍地使用管道,确保关闭所有未使用的管道通常至关重要,因为进程可能不会在需要时获得 EOF。
错误报告
在评论中,您提到了使用perror()
来报告问题。就个人而言,我不喜欢perror()
报告错误;它的格式不够强大。但是,它比某些替代方案要好。
我通常使用 GitHub 上我的SOQ(堆栈溢出问题)存储库中的一些代码作为文件stderr.c
和src/libsoq子目录中的stderr.h
代码。这对格式有广泛的控制。
err(3)
在 Linux 和 BSD(包括 macOS)上有一个概念上相似的包。我更喜欢我的,只是因为它是我的(而且因为它比err(3)
包有更强大的控制)。
控制代码ctrl61.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
if (argc != 7)
{
fprintf(stderr, "Usage: %s arg1 arg2 arg3 arg4 arg5 arg6\n", argv[0]);
exit(1);
}
int fd[2];
pipe(fd);
pid_t pid_sln1 = fork();
int sln1_status;
if (pid_sln1 < 0)
{
perror("fork error, sln1");
}
else if (pid_sln1 == 0)
{
char *paramListSln1[] =
{
"./sln1.out", argv[1], argv[2], argv[3],
argv[4], argv[5], argv[6], NULL
};
close(STDOUT_FILENO);
dup(fd[1]);
close(fd[0]);
close(fd[1]);
execv(paramListSln1[0], paramListSln1);
fprintf(stderr, "%s: failed to exec %s\n", argv[0], paramListSln1[0]);
exit(1);
}
pid_t pid_sln2 = fork();
int sln2_status;
if (pid_sln2 < 0)
{
printf("fork error, sln2.\n");
exit(1);
}
else if (pid_sln2 == 0)
{
close(STDIN_FILENO);
dup(fd[0]);
close(fd[0]);
close(fd[1]);
char in[3][50];
scanf("%49s %49s %49s", in[0], in[1], in[2]);
char *const paramListSln2[] = { "./sln2.out", in[0], in[1], in[2], NULL };
execv(paramListSln2[0], paramListSln2);
fprintf(stderr, "%s: failed to exec %s\n", argv[0], paramListSln2[0]);
exit(1);
}
close(fd[0]);
close(fd[1]);
int pid1 = wait(&sln1_status);
if (sln1_status == 0)
{
fprintf(stderr, "child process %d terminated successfully\n", pid1);
close(fd[1]);
}
else
{
fprintf(stderr, "child process %d failed 0x%.4X\n", pid1, sln1_status);
exit(1);
}
int pid2 = wait(&sln2_status);
if (sln2_status == 0)
{
fprintf(stderr, "child process %d terminated successfully\n", pid2);
close(fd[1]);
}
else
{
fprintf(stderr, "child process %d failed 0x%.4X\n", pid2, sln2_status);
exit(1);
}
return(0);
}
这段代码中有明显的重复,应该通过编写函数来修复。
请注意,此版本会在等待其中一个程序退出之前启动这两个程序。
辅助程序sln1.out.c
这与注释中假设的代码密切相关,但修复了注释使用argv[1]
但应该使用的错误argv[0]
。
#include <stdio.h>
static inline void dump_args(int argc, char **argv)
{
int argnum = 0;
fprintf(stderr, "%s: %d arguments\n", argv[0], argc);
while (*argv != 0)
fprintf(stderr, "%d: [%s]\n", argnum++, *argv++);
}
int main(int argc, char **argv)
{
dump_args(argc, argv);
if (argc != 7)
{
fprintf(stderr, "%s: incorrect argument count %d\n", argv[0], argc);
return(1);
}
printf("1 2 3\n");
return(0);
}
该程序的sln2.out.c
不同之处在于需要 3 个参数和打印321
而不是1 2 3
.
示例运行
$ ./ctrl61 abc zoo def pqr tuv 999
./sln1.out: 7 arguments
0: [./sln1.out]
1: [abc]
2: [zoo]
3: [def]
4: [pqr]
5: [tuv]
6: [999]
child process 15443 terminated successfully
./sln2.out: 4 arguments
0: [./sln2.out]
1: [1]
2: [2]
3: [3]
321
child process 15444 terminated successfully
$
这表明sln2.out
从标准输出中读取的三个参数被传递sln1.out
。
推荐阅读
- multithreading - 工作线程在 Qt 中的无限循环期间停止处理事件
- node.js - node.js 在 POST 请求上刷新 DOM
- php - 本地主机中的自定义 Artisan 命令
- php - 在一个页面上以两种不同的形式上传两个文件
- scala - 将 sbt 多项目导入 intellij 时出错
- gsutil - 通过 pip 安装的 Google Cloud SDK:“gsutil 需要 python 2.7”
- python - Scipy fmin 更新函数内的数据
- android - Android - Calendar.getInstance 的小时值设置为零
- java - 如何在 UTC 中倒计时到毫秒
- docker - 1天后AWS ECS启动容器非常慢