最小的 bison/flex 生成的代码存在内存泄漏

iaf*_*her 4 c memory-leaks bison flex-lexer

在调试一个大型项目的内存泄漏时,我发现泄漏的根源似乎是一些 flex/bison 生成的代码。我能够使用以下由两个文件组成的最小示例重新创建泄漏sand.lsand.y

sand.l

%{
#include <stdlib.h>
#include "sand.tab.h"
%}

%%
[0-9]+ { return INT; }
. ;
%%
Run Code Online (Sandbox Code Playgroud)

sand.y

%{
#include <stdio.h>
#include <stdlib.h>

int yylex();
int yyparse();
FILE* yyin;

void yyerror(const char* s);
%}

%token INT

%%
program:
       program INT { puts("Found integer"); }
       | 
       ;
%%

int main(int argc, char* argv[]) {
    yyin = stdin;
    do {
        yyparse();
    } while (!feof(yyin));
    return 0;
}

void yyerror(const char* s) {
    puts(s);
}
Run Code Online (Sandbox Code Playgroud)

该代码是用

$ bison -d sand.y
$ flex sand.l
$ gcc -g lex.yy.c sand.tab.c -o main -lfl
Run Code Online (Sandbox Code Playgroud)

使用 valgrind 运行程序出现以下错误:

8 bytes in 1 blocks are still reachable in loss record 1 of 3
at 0x4C2AC3D: malloc (vg_replace_malloc.c:299)
by 0x40260F: yyalloc (lex.yy.c:1723)
by 0x402126: yyensure_buffer_stack (lex.yy.c:1423)
by 0x400B89: yylex (lex.yy.c:669)
by 0x402975: yyparse (sand.tab.c:1114)
by 0x402EC4: main (sand.y:24)

64 bytes in 1 blocks are still reachable in loss record 2 of 3
at 0x4C2AC3D: malloc (vg_replace_malloc.c:299)
by 0x40260F: yyalloc (lex.yy.c:1723)
by 0x401CBF: yy_create_buffer (lex.yy.c:1258)
by 0x400BB3: yylex (lex.yy.c:671)
by 0x402975: yyparse (sand.tab.c:1114)
by 0x402EC4: main (sand.y:24)

16,386 bytes in 1 blocks are still reachable in loss record 3 of 3
at 0x4C2AC3D: malloc (vg_replace_malloc.c:299)
by 0x40260F: yyalloc (lex.yy.c:1723)
by 0x401CF6: yy_create_buffer (lex.yy.c:1267)
by 0x400BB3: yylex (lex.yy.c:671)
by 0x402975: yyparse (sand.tab.c:1114)
by 0x402EC4: main (sand.y:24)
Run Code Online (Sandbox Code Playgroud)

看起来 bison 和/或 flex 保留了大量的内存。有什么办法强迫他们释放它吗?

ric*_*ici 6

默认的 Flex 骨架分配一个输入缓冲区和一个小缓冲区堆栈,但它永远不会释放它们。您可以手动释放输入缓冲区,yy_delete_buffer(YY_CURRENT_BUFFER);但没有记录的方法来删除缓冲区堆栈。如果你有一个足够不古老的flex版本[参见注释1],你可以调用yylex_destroy()删除缓冲区堆栈的最后痕迹。(如果不这样做,那么您的应用程序中只有 8 个字节,所以这不是一场灾难。)

如果您想编写一个干净的应用程序,您应该生成一个可重入扫描仪,它将所有持久数据放入扫描仪上下文对象中。您的代码必须分配并释放该对象,释放它将释放所有内存分配。(您可能还想生成一个纯解析器,其工作方式大致相同。)

然而,可重入扫描器有一个非常不同的 API,因此您需要让解析器传递扫描器上下文对象。如果您还使用可重入(纯)解析器,则需要修改扫描器操作,因为使用可重入解析器时,yylval是 aYYSTYPE*而不是YYSTYPE.


笔记:

  1. 事实上,yylex_destroy()正如最近在评论中指出的那样,只要您的 Flex 版本至少为 2.5.9,您就可以使用 删除缓冲区堆栈。由于该版本是在大约二十年前发布的,因此您可能会认为本注释是不必要的,但不幸的是 v2.5.4 仍然是默认的 MinGW 安装,并且它在各种 Linux 发行版上的寿命也令人惊讶(尽管我认为这些发行版)几天你不太可能发现它已安装)。