我有一个带有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;
}
1条答案
按热度按时间hzbexzde1#
根本的问题是您没有验证
fopen
是否工作。* 每次 * 调用fopen()
后都应该检查返回值是否为NULL。否则,您永远不会注意到用户拼错了文件名。通常情况下,尝试使用NULL
FILE*
参数到stdio函数是Undefined Behaviour,这通常会导致segfault。flex扫描器注意到yyin
为NULL,并将其转换为stdin
。它这样做是因为stdin
是默认输入源(根据Posix标准)。类似地,NULLyyout
被视为stdout
。依赖Flex的这种行为可能是可以的,但它只应被有意地使用,而不是意外地使用。
如果调用应用程序时没有命令行参数,则
argc
将为1,argv[0]
将是用于调用程序的名称,而argv[1]
将为NULL。(从技术上讲,argc
可能是0,结果甚至更糟,但实际上不太可能。)然后将NULL
传递给fopen
,即未定义的行为(也就是说,一个严重的错误)。在你的标准库中fopen
的实现返回一个错误指示,而不是segfaulting [注1],但是如上所述,你没有检查这个错误返回。所以错误的复合碰巧导致yyin
为NULL,以及从stdin
阅读Flex。你应该总是检查用户输入的有效性。总是。没有例外。你应该报告错误,或者处理它们。没有任何借口。不检查是危险的,最多也就是浪费很多时间;你的和你找来帮助你的人的。
正确的代码可能如下所示:
备注
1.类Unix系统上的大多数stdio库实现
fopen
时首先调用Posix定义的open
函数。文件名只是简单地传递,因此根本不检查。open
通常是一个系统调用,因此它在内核模式下执行;这要求它将文件名从用户内存复制到内核内存,而内核内存又要求它首先验证地址。因此,在Unix上,将一个无效的字符串指针传递给fopen
可能会产生某种错误指示。任何标准都不要求这样做,也没有使用errno
代码的规范。在非Posix平台上可能不是这样,在将文件路径传递到本机文件系统之前,fopen
很可能需要以某种方式转换文件路径。(例如,它可能需要将/
目录分隔符转换为其他内容。)在这样的系统上,很可能不会检查filename参数的有效性,并且当fopen
库函数试图使用无效的文件名指针时,它将出现segfault(或等效的)。在大多数常见的Unix stdio库实现中,如果
mode
参数被指定为NULL
,fopen
将segfault。像所有库函数一样,fopen
没有义务科普NULL
指针参数; C标准坚持认为将NULL
作为指针参数传递给任何库函数都是未定义的行为,除非该库函数被显式地记录为接受NULL
作为该参数。(例如,参见free
、realloc
和strtok
,了解明确允许NULL
的库函数。)fopen
不是这样的函数,因此不应将NULL
作为任何参数传递,当然也不应假设结果只是返回错误。