我对yyrestart的函数签名感到好奇 - 也就是在lexer文件中我看到签名是:
void yyrestart (FILE * input_file )
在我的代码中,我使用yyrestart来刷新缓冲区,但我还没有传递任何参数,它只是空的:
yyrestart();
目前我们测试的每个系统都在使用,除了最新版本的OS X.通过GDB,在我的rhel机器上清楚地看到,只是无参数调用将文件指针设置为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文件中所执行的解析器(这是对{{3}的轻微修改}}):
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编译整个程序(但是输入带有错误输入的segv)。但是通过生成的parcalc.c文件,我看不到任何允许我用除文件指针之外的任何东西调用calcYYrestart的东西。我只把它看作原型:
void calcYYrestart (FILE * input_file );
编译器发生了什么,让我把我想要的东西作为参数添加到生成的函数中?
答案 0 :(得分:2)
你期待C轻轻地引导你穿过迷宫,握住你的手,当你犯错并责备你的成功时责备你。
这些可能不是对语言的不合理期望,但C不是那种语言。 C做了你告诉它做的事情,仅此而且,当你的指示不明确时,它只会让你跌倒。
虽然,在它的辩护中,你可以要求它更冗长一点。如果在命令行中指定-Wall
(至少使用gcc和clang),编译器将为您提供一些警告。 [见注1]
在这种情况下,它可能会警告你calcYYrestart
未被声明,这将使你有责任让正确的论点。该函数在词法分析器中声明和定义,但是您在解析器中使用它,它是一个单独的编译单元。你真的应该在解析器序言中声明它,但没有什么能强制执行该声明的正确性。 (在这种情况下,C ++将无法链接,但C不会在正式函数名中记录参数类型。)
值得注意的是,您正在开展工作的示例代码存在许多问题。我建议寻找一个更好的野牛/弹性教程,或者至少阅读flex手册中关于如何处理输入的部分。
在这里,我在原始示例中添加了一些注释,其中显示了calc.y
bison输入文件:
/* 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扫描程序通常以大块(交互式输入除外)读取输入,因此使用{{重置输入文件1}}将丢弃随机数量的输入,将输入文件指针留在文件中的随机位置,这可能与新行的开头不对应。
即使不是这种情况,与无缓冲(交互式)输入一样,完全可能是在行的末尾检测到错误,在这种情况下新行已经已被消耗。因此,丢弃到当前行的末尾将导致丢失错误后的行。
最后,使用yyrestart
来终止输入循环是众所周知的反模式,应该避免在读取输入时遇到EOF时终止。对于flex生成的扫描仪,当检测到EOF时,将丢弃当前输入,然后(如果feof(input)
未成功创建新输入),则yywrap
指示为返回解析器。到那时,END
不再有效(因为它已被丢弃),并且在其上调用yyin
是未定义的行为。
feof
来获得更多警告。并且你可以通过告诉它使用最新的标准-Wextra
而不是使用各种gcc扩展增加的1989版本来使编译器更严格一些,现在大多数已经过时了。)