从 flex/bison 中释放在 strdup() 中分配的字符串
freeing the string allocated in strdup() from flex/bison
我有使用 strdup()
复制字符串词素的 flex 代码。
%{
#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()
分配内存并将输入字符串复制到其中和 return (strdup() - what does it do in C?),所以我想当我不再需要它时我需要释放它.
从这个post:When is %destructor invoked in BISON?,我在yacc文件中添加了%destructor { free($$); printf("free");} STRING
。
但是,我没有看到 free()
被调用,即使 yylval.sval
被分配了来自 strdup()
的新字符串 return。
可能出了什么问题?如何释放 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;
}
正如您所说,您需要释放字符串 "when I don't need it anymore." 就这么简单(或复杂)。
C 没有垃圾收集器,因此 C 程序员有责任知道何时不再需要分配的内存。语言不会试图弄明白,而且(大部分)野牛也不会。
如果您有一个缩减规则,该规则提供了一个或多个包含指向已分配内存的指针的语义值,则该规则可能会执行许多操作中的任何一项。它可能会将语义值传递给新的语义值,通常是通过仅复制指针。它可能会复制语义值,然后释放原始值。它可能会将语义值添加到解析全局数据结构中,例如符号 table.
在所有这些情况下,程序员应该知道分配的内存是否仍然需要,如果不需要,应该调用 free 分配。
但是,在某些情况下,野牛会丢弃语义值,而不会将其呈现给缩减操作。其中大部分是错误情况。如果作为错误恢复的一部分,bison 决定丢弃一个标记,则该标记的语义值可能会泄漏内存。而正是针对这种情况,bison 有一个 %destructor
声明。如果(且仅当)bison 由于错误恢复或 post-错误清理而丢弃令牌,则调用 %destructor
代码。所有其他情况由您负责。
试图通过使堆栈槽变得巨大(例如在语义值联合中包含 char[100]
)来逃避这一责任既不安全又低效。这是不安全的,因为您需要时刻注意固定的 space 缓冲区可能会溢出,这意味着解析语法上有效的程序可能会覆盖任意内存。这是低效的,因为您最终会使堆栈比需要的大几个数量级;并且还因为您最终会不断复制堆栈槽(对于 每个 减少规则至少复制两次,即使是那些使用默认操作的规则。)
只有当您打算共享内存时,计算出语义值的生命周期才会变得复杂。这对于字符串文字通常没有用(如您的示例所示),但对于变量名可能非常有用;大多数名称在一个程序中出现不止一次,因此每次出现都使用相同的字符串。
我通常通过 "interning" 词法分析器中的字符串来解决标识符问题。词法分析器维护一个解析全局名称 table —— 比如说,一个简单的 set
用散列 table 实现的 —— 对于它遇到的每个标识符,它将标识符添加到名称中table 并将唯一名称条目指针作为语义值传递。在解析结束后的某个时刻,可以释放整个名称 table,释放所有标识符。
对于字符串文字和其他可能唯一的字符串,您可以使用名称 table 无论如何,或者您可以避免拥有指向同一字符串的指针的两个副本。使用名称 table 的好处是可以减少您在内存管理中需要做的工作量,但代价是可能会在额外的时间内保留不必要的字符串。这在很大程度上取决于解析结果的性质:如果它是 AST,那么只要 AST 存在,您可能就需要保留字符串,但如果您正在进行直接执行或一次性代码生成,您可能从长远来看不需要字符串文字。
我有使用 strdup()
复制字符串词素的 flex 代码。
%{
#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()
分配内存并将输入字符串复制到其中和 return (strdup() - what does it do in C?),所以我想当我不再需要它时我需要释放它.
从这个post:When is %destructor invoked in BISON?,我在yacc文件中添加了%destructor { free($$); printf("free");} STRING
。
但是,我没有看到 free()
被调用,即使 yylval.sval
被分配了来自 strdup()
的新字符串 return。
可能出了什么问题?如何释放 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;
}
正如您所说,您需要释放字符串 "when I don't need it anymore." 就这么简单(或复杂)。
C 没有垃圾收集器,因此 C 程序员有责任知道何时不再需要分配的内存。语言不会试图弄明白,而且(大部分)野牛也不会。
如果您有一个缩减规则,该规则提供了一个或多个包含指向已分配内存的指针的语义值,则该规则可能会执行许多操作中的任何一项。它可能会将语义值传递给新的语义值,通常是通过仅复制指针。它可能会复制语义值,然后释放原始值。它可能会将语义值添加到解析全局数据结构中,例如符号 table.
在所有这些情况下,程序员应该知道分配的内存是否仍然需要,如果不需要,应该调用 free 分配。
但是,在某些情况下,野牛会丢弃语义值,而不会将其呈现给缩减操作。其中大部分是错误情况。如果作为错误恢复的一部分,bison 决定丢弃一个标记,则该标记的语义值可能会泄漏内存。而正是针对这种情况,bison 有一个 %destructor
声明。如果(且仅当)bison 由于错误恢复或 post-错误清理而丢弃令牌,则调用 %destructor
代码。所有其他情况由您负责。
试图通过使堆栈槽变得巨大(例如在语义值联合中包含 char[100]
)来逃避这一责任既不安全又低效。这是不安全的,因为您需要时刻注意固定的 space 缓冲区可能会溢出,这意味着解析语法上有效的程序可能会覆盖任意内存。这是低效的,因为您最终会使堆栈比需要的大几个数量级;并且还因为您最终会不断复制堆栈槽(对于 每个 减少规则至少复制两次,即使是那些使用默认操作的规则。)
只有当您打算共享内存时,计算出语义值的生命周期才会变得复杂。这对于字符串文字通常没有用(如您的示例所示),但对于变量名可能非常有用;大多数名称在一个程序中出现不止一次,因此每次出现都使用相同的字符串。
我通常通过 "interning" 词法分析器中的字符串来解决标识符问题。词法分析器维护一个解析全局名称 table —— 比如说,一个简单的 set
用散列 table 实现的 —— 对于它遇到的每个标识符,它将标识符添加到名称中table 并将唯一名称条目指针作为语义值传递。在解析结束后的某个时刻,可以释放整个名称 table,释放所有标识符。
对于字符串文字和其他可能唯一的字符串,您可以使用名称 table 无论如何,或者您可以避免拥有指向同一字符串的指针的两个副本。使用名称 table 的好处是可以减少您在内存管理中需要做的工作量,但代价是可能会在额外的时间内保留不必要的字符串。这在很大程度上取决于解析结果的性质:如果它是 AST,那么只要 AST 存在,您可能就需要保留字符串,但如果您正在进行直接执行或一次性代码生成,您可能从长远来看不需要字符串文字。