在野牛中调用yyrestart函数,没有任何参数导致El Capitan上的sigsev

时间:2015-10-27 19:17:25

标签: c bison flex-lexer

我对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 );

编译器发生了什么,让我把我想要的东西作为参数添加到生成的函数中?

1 个答案:

答案 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是未定义的行为。

注释

  1. 您还可以指定feof来获得更多警告。并且你可以通过告诉它使用最新的标准-Wextra而不是使用各种gcc扩展增加的1989版本来使编译器更严格一些,现在大多数已经过时了。)