【问题标题】:freeing the string allocated in strdup() from flex/bison从 flex/bison 中释放 strdup() 中分配的字符串
【发布时间】:2015-09-15 05:52:28
【问题描述】:

我有使用strdup() 复制字符串词位的弹性代码。

%{   
#include "json.tab.h"
#define YY_DECL extern "C" int yylex()

%}
%option noyywrap

%%

[ \t\n]+ ; 
\"[a-zA-Z]+\" {yylval.sval = strdup(yytext); return STRING; }
[0-9]+ {yylval.ival = atoi(yytext); return NUMBER; }
. {return yytext[0];} ; 

%%

strdup() 分配内存并将输入字符串复制到其中并返回(strdup() - what does it do in C?),所以我想当我不再需要它时需要释放它。

从这个帖子:When is %destructor invoked in BISON?,我在 yacc 文件中添加了%destructor { free($$); printf("free");} STRING

但是,即使为yylval.sval 分配了从strdup() 返回的新字符串,我也没有看到free() 被调用。

可能出了什么问题?如何释放flex/bison中分配的字符串?

添加

我考虑使用静态分配的sval如下:

%union {
    int ival;
    char sval[100]; // char* sval;
}

flex 代码现在变为(如果 yytext 小于 100 字节,则没有检查代码):

\"[a-zA-Z]+\" {
    //yylval.sval = strdup(yytext);
    memset(yylval.sval, 0, 100);
    strcpy(yylval.sval, yytext);
    return STRING; 
}

我不确定这种方法是否是人们通常使用的方法。

添加2

对于我的申请,简单的实习是可以的。

extern char buffer[]; // [100];
%}
%option noyywrap

%%

\"[a-zA-Z]+\" {
        //yylval.sval = strdup(yytext);
        memset(buffer, 0, 100);
        strcpy(buffer, yytext);
        yylval.sval = buffer;
        return STRING; 
    }
...

char buffer[100];

对于 yacc 代码

%union {
    int ival;
    char *sval; 
}

【问题讨论】:

  • 你知道c和c++不是一回事吗?不要在 C++ 中使用 strdup(),使用 std::string 并忘记为字符串分配或释放内存。
  • @iharob:我想我不明白你的回答。您能否详细说明如何使用 std::string 将其分配给 yylval 变量?
  • 我不记得怎么做了,但肯定有可能。问题是您正在生成一个 c 解析器,并使用 c++ 代码。阅读有关 c++ 解析器的信息。
  • @iharob:你能做的最好的就是在联合中放置一个 std::string *,这需要你仍然关心内存管理。

标签: c bison yacc flex-lexer strdup


【解决方案1】:

正如您所说,您需要释放字符串“当我不再需要它时”。就这么简单(或复杂)。

C 没有垃圾收集器,因此 C 程序员有责任知道何时不再需要分配的内存。该语言不会试图弄清楚它,而且(大部分)野牛也不会。

如果您有一个缩减规则,它提供了一个或多个包含指向已分配内存的指针的语义值,那么该规则可能会做很多事情。它可能会将语义值传递给新的语义值,通常只复制指针。它可能会复制语义值,然后释放原始值。它可能会将语义值添加到解析全局数据结构中,例如符号表。

在所有这些情况下,程序员都应该知道分配的内存是否仍然需要,如果不需要,应该调用 free 分配的内存。

但是,在少数情况下,bison 会丢弃语义值,而不会将其呈现给归约操作。其中大部分是错误条件。如果作为错误恢复的一部分,bison 决定丢弃一个标记,则该标记的语义值可能会泄漏内存。正是在这种情况下,bison 有一个%destructor 声明。 %destructor 代码在(且仅当)bison 由于错误恢复或错误后清理而丢弃令牌时被调用。所有其他情况由您负责。

试图通过使堆栈槽变得巨大(例如在语义值联合中包含char[100])来逃避这一责任既不安全又低效。这是不安全的,因为您需要经常注意固定空间缓冲区可能会溢出,这意味着解析语法上有效的程序可能会覆盖任意内存。这是低效的,因为您最终使堆栈比必要的大几个数量级;也因为你最终会不断地复制堆栈槽(对于每个缩减规则至少两次,即使是使用默认操作的那些。)

仅当您打算共享内存时,才能确定语义值的生命周期。这通常对字符串文字没有用(如您的示例中),但它对变量名很有帮助;大多数名称在一个程序中出现不止一次,因此总是很容易对每次出现使用相同的字符串。

我通常通过在词法分析器中“实习”字符串来解决标识符问题。词法分析器维护一个解析全局名称表——比如说,一个用哈希表实现的简单set——并且对于它遇到的每个标识符,它将标识符添加到名称表中,并将唯一名称条目指针作为语义价值。在解析结束后的某个时间点,可以释放整个名称表,释放所有标识符。

对于字符串文字和其他可能唯一的字符串,您可以使用名称表,也可以避免拥有指向同一字符串的指针的两个副本。使用名称表的好处是可以减少您在内存管理中需要做的工作量,但代价是可能会保留不必要的字符串以延长时间。这在很大程度上取决于解析结果的性质:如果它是 AST,那么只要 AST 存在,您可能需要保留字符串,但如果您正在执行直接执行或一次性代码生成,您可能从长远来看,不需要字符串文字。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-04-27
    • 1970-01-01
    • 1970-01-01
    • 2015-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多