在没有参数的情况下在 bison 中调用 yyrestart 函数导致 El Capitan 上出现 sigsegv

Calling yyrestart function in bison with no arguments causing sigsev on El Capitan

我很好奇 yyrestart 的函数签名 - 即在词法分析器文件中我看到签名是:

void yyrestart  (FILE * input_file )

在我的代码中,我使用 yyrestart 刷新缓冲区,但我没有向它传递任何参数,它只是空的:

yyrestart();

除了最新版本的 OS X,它目前在我们测试的每个系统上都在运行。通过 GDB 单步执行,很明显在我的 rhel 机器上,不带参数的调用将文件指针设置为空值:

yyrestart (input_file=0x0) at reglexer.c:1489

而在 El Capitan 上它作为垃圾出现,这导致稍后在生成的代码中出现内存错误:

yyrestart (input_file=0x100001d0d) at reglexer.c:1489

我这辈子都弄不清楚 yyrestart() 的定义位置。 yacc/flex 中是否有一些宏定义了不带参数调用 yyrestart 的行为?如果不是,这怎么编译?

*********** 编辑以澄清编译问题 ************

作为一个小片段来了解我在说什么 - 这是我的 .y 文件中的内容,它正在执行解析器(这是对 this example 的内容的轻微修改):

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

编译器让我可以将我想要的任何内容作为生成函数的参数的神奇之处在哪里?

你期待着C温柔地带领你穿过迷宫,牵着你的手,犯错时责备你,成功时为你鼓掌。

这些可能不是对一门语言的不合理期望,但C不是那门语言。 C 会按照您的指示进行操作,仅此而已,当您的指令不够清晰时,它只会让您跌倒。

虽然,在辩护中,您可以要求它更冗长一点。如果您在命令行中指定 -Wall(至少使用 gcc 和 clang),编译器将为您提供一些警告。 [见注释 1。]

在这种情况下,它可能会警告您未声明 [​​=12=],这将使您有责任使参数正确。该函数是在词法分析器中声明和定义的,但在这里您是在解析器中使用它,这是一个单独的编译单元。你真的应该在解析器序言中声明它,但没有任何东西会强制声明的正确性。 (在那种情况下,C++ 将无法 link,但 C 不会在正式函数名称中记录参数类型。)


值得注意的是,您作为工作基础的示例代码存在很多问题。我建议寻找更好的 bison/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 returns 立即(在清理其本地存储后)并显示错误指示。如果你想让它继续,那么你需要使用一个 error 生产,这将是最好的解决方案。

您也可以再次调用 yyparse,如示例所示。但是,这会在 flex 缓冲区中留下未知数量的输入文件。 没有理由相信缓冲区完全包含错误行的其余部分。由于 flex 扫描仪通常以大块读取那里的输入(交互式输入除外),因此重置输入文件with yyrestart 将丢弃随机数量的输入,将输入文件指针留在文件中的随机位置,这可能与新行的开头不对应。

即使情况并非如此,对于无缓冲(交互式)输入,也完全有可能检测到错误行尾,在这种情况下新行将已经被消耗。因此丢弃到当前行的末尾将导致丢弃错误之后的行。

最后,使用 feof(input) 终止输入循环是众所周知的反模式,应该避免在读取输入时遇到 EOF 时终止。对于 flex 生成的扫描器,当检测到 EOF 时,丢弃当前输入,然后(如果 yywrap 没有成功创建新输入),返回 END 指示解析器。到那时,yyin 不再有效(因为它已被丢弃),调用 feof 是未定义的行为。


备注

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