unix main需要一个文件名作为第一个参数...但是我也可以通过一个管道向main提供文件...这是如何工作的?

nsc4cvqm  于 2022-11-04  发布在  Unix
关注(0)|答案(1)|浏览(161)

我有一个带有main()函数的C程序:

int main(int argc, char *argv[])
{ 
    FILE *f = fopen(argv[1], "r");
    ...
}

注意,当执行程序时,它期望提供文件名作为第一参数,例如,

main test.dat

当我这样运行时,程序工作得很好。
有趣的是,当我这样运行程序时,它也能正常工作:

cat test.dat | main

这不是给main()提供一个文件名,而是将test.dat的内容流到main(),对吗?那么它是如何工作的呢?
进一步阐述:main()函数是野牛解析器中的main函数。我在下面展示了main()函数。正如我提到的,无论我用什么方法调用它,解析器都能正常工作:

main test.dat

或者这样:

cat test.dat | main

下面是解析器的main()函数:

int main(int argc, char *argv[])
{ 
    yyin = fopen(argv[1], "r");
    yyparse();
    fclose(yyin);
    return 0;
}
hzbexzde

hzbexzde1#

根本的问题是您没有验证fopen是否工作。* 每次 * 调用fopen()后都应该检查返回值是否为NULL。否则,您永远不会注意到用户拼错了文件名。
通常情况下,尝试使用NULL FILE*参数到stdio函数是Undefined Behaviour,这通常会导致segfault。flex扫描器注意到yyin为NULL,并将其转换为stdin。它这样做是因为stdin是默认输入源(根据Posix标准)。类似地,NULL yyout被视为stdout
依赖Flex的这种行为可能是可以的,但它只应被有意地使用,而不是意外地使用。
如果调用应用程序时没有命令行参数,则argc将为1,argv[0]将是用于调用程序的名称,而argv[1]将为NULL。(从技术上讲,argc可能是0,结果甚至更糟,但实际上不太可能。)然后将NULL传递给fopen,即未定义的行为(也就是说,一个严重的错误)。在你的标准库中fopen的实现返回一个错误指示,而不是segfaulting [注1],但是如上所述,你没有检查这个错误返回。所以错误的复合碰巧导致yyin为NULL,以及从stdin阅读Flex。
你应该总是检查用户输入的有效性。总是。没有例外。你应该报告错误,或者处理它们。没有任何借口。不检查是危险的,最多也就是浪费很多时间;你的和你找来帮助你的人的。
正确的代码可能如下所示:

if (argc > 1) {
        yyin = fopen(argv[1], "r");
        if (yyin == NULL) {
            fprintf("Could not open file '%s': %s\n",
                     argv[1], strerror(errno));
            exit(1);
        }
    }
    else {
        /* argc <= 1, so there was no command line argument.
         * Read from stdin.
         */
        yyin = stdin;
    }

备注

1.类Unix系统上的大多数stdio库实现fopen时首先调用Posix定义的open函数。文件名只是简单地传递,因此根本不检查。open通常是一个系统调用,因此它在内核模式下执行;这要求它将文件名从用户内存复制到内核内存,而内核内存又要求它首先验证地址。因此,在Unix上,将一个无效的字符串指针传递给fopen可能会产生某种错误指示。任何标准都不要求这样做,也没有使用errno代码的规范。在非Posix平台上可能不是这样,在将文件路径传递到本机文件系统之前,fopen很可能需要以某种方式转换文件路径。(例如,它可能需要将/目录分隔符转换为其他内容。)在这样的系统上,很可能不会检查filename参数的有效性,并且当fopen库函数试图使用无效的文件名指针时,它将出现segfault(或等效的)。
在大多数常见的Unix stdio库实现中,如果mode参数被指定为NULLfopensegfault。像所有库函数一样,fopen没有义务科普NULL指针参数; C标准坚持认为将NULL作为指针参数传递给任何库函数都是未定义的行为,除非该库函数被显式地记录为接受NULL作为该参数。(例如,参见freereallocstrtok,了解明确允许NULL的库函数。)fopen不是这样的函数,因此不应将NULL作为任何参数传递,当然也不应假设结果只是返回错误。

相关问题