首页 文章

使用scanf从管道读取失败

提问于
浏览
0

在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 失败了...

1 回答

  • 1

    主要问题 - 未初始化的指针

    当我使用命令行编译问题(源文件, ctrl61.c )中的代码时:

    gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes ctrl61.c -o ctrl61
    

    (GCC 8.2.0在带有macOS 10.14.2 Mojave的Mac上运行),我得到了如下警告:

    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 修饰符 %s 和其他相应的变化,使 scanf() 分配内存给你 . 请注意,某些系统(例如macOS)不支持POSIX强制的sscanf()修饰符 .

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

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

    如果使用dup()fcntl()与`F_DUPFD复制描述符,则该规则也适用 .

    在这个程序中,它可能无关紧要,但如果您更普遍地使用管道,确保关闭所有未使用的管道通常是至关重要的,因为进程可能无法在需要时获得EOF .

    错误报告

    在评论中,您提到使用 perror() 来报告问题 . 就个人而言,我不喜欢 perror() 报告错误;它的格式不够强大 . 但是,它比一些替代品更好 .

    我通常在GitHub上的SOQ(Stack Overflow Questions)存储库中使用一些代码作为 stderr.c 子目录中的文件 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 的标准输出读取的三个参数 .

相关问题