我对yyrestart的函数签名很好奇--即在lexer文件中,我看到签名是:
void yyrestart (FILE * input_file )
在我的代码中,我使用yyrestart刷新缓冲区,但我没有传递任何参数,它只是空的:
yyrestart();
它目前正在我们测试的每个系统上运行,除了最新版本的OS。通过GDB,在我的流变机上可以清楚地看到,在没有参数调用的情况下,文件指针设置为NULL:
yyrestart (input_file=0x0) at reglexer.c:1489
而在El Capitan上,它作为垃圾出现,这将导致生成的代码后面出现mem错误:
yyrestart (input_file=0x100001d0d) at reglexer.c:1489
在我的一生中,我无法确定yyrestart()是在哪里定义的。在yacc/flex中是否有一些宏定义了不带参数调用yyrestart的行为?如果没有,这是如何编译的呢?
*编辑澄清编译问题*
作为一个小片段来了解我在说什么--这就是我的.y文件中的内容,它正在执行解析器(这是对这个例子的一个微小的修改):
int main() {
FILE *myfile = fopen("infile.txt", "r");
if (!myfile) {
fprintf(stderr, "can't open infile.txt\n");
return 1;
}
calcYYin = myfile;
do {
calcYYparse();
} while (!feof(calcYYin));
calcYYrestart();
return 0;
}
我可以用我想要的任何东西来构建存储库,并将其作为参数传入到该行上的calcYYrestart()。代用
calcYYrestart('a', 1, 5, 'a string');
仍然允许我使用make编译整个程序(但是get是一个输入错误的片段)。但是在查看生成的parcalc.c文件时,除了文件指针之外,没有任何东西允许我调用calcYYrestart。我只把它看作是原型:
void calcYYrestart (FILE * input_file );
编译器的神奇之处在于,它允许我将任何我想要的东西作为参数放到生成的函数中?
发布于 2015-10-28 00:17:08
你期待着C能轻轻地带领你穿过迷宫,握着你的手,当你犯错的时候责备你,并为你的成功鼓掌。
这可能不是对一种语言的不合理的期望,但C不是那种语言。做你让它做的事情,没有更多,当你的指令不够清晰时,它只会让你跌倒。
不过,作为辩护,你可以要求它再详细一点。如果在命令行中指定-Wall
(至少使用gcc和clang),编译器将向您提供一些警告。见附注1。
在这种情况下,它可能会警告您没有声明calcYYrestart
,这将使您有责任正确地处理参数。这个函数是在lexer中声明和定义的,但是在这里您要在解析器中使用它,解析器是一个单独的编译单元。您确实应该在解析器序言中声明它,但是没有什么可以强制声明的正确性。(在这种情况下,C++将无法链接,但是C不会在形式函数名称中记录参数类型。)
值得注意的是,您的工作所依据的示例代码存在许多问题。我建议寻找一个更好的野牛/ flex教程,或者至少阅读flex手册中关于如何处理输入的章节。
在这里,我向原始示例添加了一些注释,其中显示了calc.y
野牛输入文件:
/* This is unnecessary, since `calcYYparse` is defined in this file.
extern int calcYYparse();
*/
extern FILE *calcYYin;
/* Command line arguments are always good */
int main(int argc, char** argv) {
/* If there is an argument, use it. Otherwise, stick with stdin */
/* There is no need for a local variable. We can just use yyin */
if (argc > 1) {
calcYYin = fopen(argv[1], "r");
if (!calcYYin) {
fprintf(stderr, "can't open infile.txt\n");
return 1;
}
}
/* calcYYin = myfile; */
/* This loop is unnecessary, since yyparse parses input until it
* reaches EOF, unless it hits an error. And if it hits an error, it
* will call calcYYerror (below), which in turn calls exit(1), so it
* never returns.
*/
/* do { */
calcYYparse();
/* } while (!feof(calcYYin)); */
return 0;
}
void calcYYerror(const char* s) {
fprintf(stderr, "Error! %s\n", s);
/* Valid arguments to `exit` are 0 and small positive integers. */
exit(EXIT_FAILURE);
}
当然,如果你碰到语法错误,你可能不想把这个世界搞砸。这样做的目的可能是放弃行的其余部分,然后继续解析。在这种情况下,出于明显的原因,callYYerror
不应该调用exit()
。
默认情况下,在调用yyerror
之后,yyparse
会立即返回(在清理其本地存储之后),并带有错误指示。如果您想继续使用它,那么您需要使用error
产品,这将是最好的解决方案。
您也可以简单地再次调用yyparse
,如本例所示。但是,这在flex缓冲区中留下了未知数量的输入文件。没有理由相信缓冲区错误地包含了行的其余部分。由于flex扫描器通常以大块形式读取输入(交互输入除外),用yyrestart
重置输入文件将丢弃随机数量的输入,将输入文件指针留在文件中的任意位置,这可能与新行的开头不对应。
即使不是这种情况,就像非缓冲(交互式)输入一样,完全有可能在行尾检测到错误,在这种情况下,新行已经被消耗掉了。因此,丢弃到当前行的末尾将导致在错误之后丢弃行。
最后,使用feof(input)
终止输入循环是众所周知的反模式,应该避免在读取输入时遇到EOF时终止。对于flex生成的扫描器,当检测到EOF时,将丢弃当前的输入,然后(如果yywrap
不能成功地创建新的输入),END
指示将返回到解析器。到那时,yyin
不再有效(因为它被丢弃了),对它调用feof
是未定义的行为。
备注
-Wextra
来获得更多的警告。你可以让编译器更严格一点,让它使用最新的标准,-std=c11
,而不是1989年版本的各种gcc扩展,现在大多已经过时了。)https://stackoverflow.com/questions/33376700
复制相似问题