繁体   English   中英

使用scanf从管道读取失败

[英]failed using scanf to read from pipe

在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。)或者您可以对%s和其他相应更改使用m修饰符,以便scanf()为您分配内存。 请注意,某些系统(例如macOS)不支持POSIX要求的sscanf()修饰符。

您没有在子级(或实际上在父级)中关闭足够的文件描述符。

经验法则 :如果将管道的一端dup2()为标准输入或标准输出,请尽快关闭pipe()返回的两个原始文件描述符。 特别是,在使用任何exec*()系列函数之前,应关闭它们。

如果您用带有F_DUPFD的dup()fcntl()复制描述符,则该规则也适用。

在此程序中,可能并不重要,但是如果您更一般地使用管道,确保关闭所有未使用的管道通常至关重要,因为在必要时进程可能无法获得EOF。

错误报告

在评论中,您提到使用perror()报告问题。 就个人而言,我不喜欢perror()报告错误; 其格式不够强大。 但是,它比某些替代方法要好。

我通常使用GitHub上我的SOQ (堆栈溢出问题)存储库中的某些代码,作为src / libsoq子目录中的stderr.cstderr.h文件。 这具有对格式的广泛控制。

在Linux和BSD(包括macOS)上有一个概念类似的软件包err(3) )。 我更喜欢我的,仅仅是因为它是我的(并且因为它比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的标准输出读取的三个参数。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM