BNFC 生成的 C 解析器中的内存管理
memory management in C parser generated by BNFC
我使用 BNFC 生成解析器 bnfc -m -c ./mylang.cf
。在内部,BNFC makefile 调用 bison 来生成 C 解析器。
Parser.c : mylang.y
${BISON} ${BISON_OPTS} mylang.y -o Parser.c
我可以通过调用下面生成的psProc
方法成功解析源代码。
/* Global variables holding parse results for entrypoints. */
Proc YY_RESULT_Proc_ = 0;
// ...
/* Entrypoint: parse Proc from string. */
Proc psProc(const char *str)
{
YY_BUFFER_STATE buf;
mylang__init_lexer(0);
buf = mylang__scan_string(str);
int result = yyparse();
mylang__delete_buffer(buf);
if (result)
{ /* Failure */
return 0;
}
else
{ /* Success */
return YY_RESULT_Proc_;
}
}
struct Proc_;
typedef struct Proc_ *Proc;
struct Proc_
{
enum { is_PGround, is_PCollect, is_PVar, is_PVarRef, is_PNil, is_PSimpleType, is_PNegation, is_PConjunction, is_PDisjunction, is_PEval, is_PMethod, is_PExprs, is_PNot, is_PNeg, is_PMult, is_PDiv, is_PMod, is_PPercentPercent, is_PAdd, is_PMinus, is_PPlusPlus, is_PMinusMinus, is_PLt, is_PLte, is_PGt, is_PGte, is_PMatches, is_PEq, is_PNeq, is_PAnd, is_POr, is_PSend, is_PContr, is_PInput, is_PChoice, is_PMatch, is_PBundle, is_PIf, is_PIfElse, is_PNew, is_PPar } kind;
union
{
struct { Ground ground_; } pground_;
struct { Collection collection_; } pcollect_;
struct { ProcVar procvar_; } pvar_;
struct { Var var_; VarRefKind varrefkind_; } pvarref_;
struct { SimpleType simpletype_; } psimpletype_;
struct { Proc proc_; } pnegation_;
struct { Proc proc_1, proc_2; } pconjunction_;
struct { Proc proc_1, proc_2; } pdisjunction_;
struct { Name name_; } peval_;
struct { ListProc listproc_; Proc proc_; Var var_; } pmethod_;
struct { Proc proc_; } pexprs_;
struct { Proc proc_; } pnot_;
struct { Proc proc_; } pneg_;
struct { Proc proc_1, proc_2; } pmult_;
struct { Proc proc_1, proc_2; } pdiv_;
struct { Proc proc_1, proc_2; } pmod_;
struct { Proc proc_1, proc_2; } ppercentpercent_;
struct { Proc proc_1, proc_2; } padd_;
struct { Proc proc_1, proc_2; } pminus_;
struct { Proc proc_1, proc_2; } pplusplus_;
struct { Proc proc_1, proc_2; } pminusminus_;
struct { Proc proc_1, proc_2; } plt_;
struct { Proc proc_1, proc_2; } plte_;
struct { Proc proc_1, proc_2; } pgt_;
struct { Proc proc_1, proc_2; } pgte_;
struct { Proc proc_1, proc_2; } pmatches_;
struct { Proc proc_1, proc_2; } peq_;
struct { Proc proc_1, proc_2; } pneq_;
struct { Proc proc_1, proc_2; } pand_;
struct { Proc proc_1, proc_2; } por_;
struct { ListProc listproc_; Name name_; Send send_; } psend_;
struct { ListName listname_; Name name_; NameRemainder nameremainder_; Proc proc_; } pcontr_;
struct { Proc proc_; Receipt receipt_; } pinput_;
struct { ListBranch listbranch_; } pchoice_;
struct { ListCase listcase_; Proc proc_; } pmatch_;
struct { Bundle bundle_; Proc proc_; } pbundle_;
struct { Proc proc_1, proc_2; } pif_;
struct { Proc proc_1, proc_2, proc_3; } pifelse_;
struct { ListNameDecl listnamedecl_; Proc proc_; } pnew_;
struct { Proc proc_1, proc_2; } ppar_;
} u;
};
我有几个关于 Proc psProc(const char *str)
的问题。
我可以在 psProc
returns 之后立即释放 char *str
参数引用的源缓冲区吗?我猜返回的 Proc
可能包含指向输入源缓冲区的指针,所以我应该确保源缓冲区的生命周期比返回的指针长。对吗?
我应该如何释放返回的Proc
?返回的Proc
是一个指向Proc_
的指针,由指针组成一个抽象语法树。我只需要在返回的指针上调用一次 free()
就可以释放,对吗?
在Proc psProc(const char *str)
的方法体中,returns一个指针存储在全局变量YY_RESULT_Proc_
中。这是否意味着我不能从不同线程同时调用 psProc
?
此类问题应在该工具的文档中得到解答。但是我在那里找不到它们:-(我也没有太多使用 BNFC 的经验,所以在应用这个答案时要谨慎。
psProc
调用词法分析器的 scan_string
接口,该接口复制提供的字符串。 Flex 喜欢在标记化时修改输入,因此它只能通过复制来处理 const char*
个输入。因此,字符串可以在调用 scan_string
后立即释放,但 psProc
在返回之前解析整个输入,因此您无论如何都不必这样做。当 psProc
returns.
时,您当然可以释放字符串
我怀疑这对你来说是否是个问题,但如果你计划解析非常大的内存中字符串,你可能需要考虑使用 fmemopen
(至少,在 Posix平台)将字符串打开为 FILE*
。这并没有避免复制,但它以 8k 左右的块进行复制,这避免了在解析期间保留整个字符串的两个副本。
我不知道 BNFC 如何期望您释放解析树节点。 (事实上,我宁愿怀疑它不希望你那样做。)节点与内部指针链接,当然可以编写一个 AST walker,它使用 [= 递归地释放所有节点42=]-顺序遍历。但是我没有看到任何生成的代码可以做到这一点。可能是我看的不够仔细
在顶级节点上调用free()
只会释放一个节点。树的其余部分将被泄露,因为不存在指向它的其他指针。
我很确定您对线程安全的怀疑是正确的。全局由 Proc
生产的缩减操作分配给,然后由 psProc
返回。没有锁定,所以如果另一个线程中有另一个解析器,则全局可能会被覆盖。全局只是指向要返回的节点的指针,节点本身应该是线程安全的,因为它是由解析器线程动态分配的。因此,您可能会更改全局的声明以使用线程本地存储,但这必须通过 post 处理生成的代码来完成。
我使用 BNFC 生成解析器 bnfc -m -c ./mylang.cf
。在内部,BNFC makefile 调用 bison 来生成 C 解析器。
Parser.c : mylang.y
${BISON} ${BISON_OPTS} mylang.y -o Parser.c
我可以通过调用下面生成的psProc
方法成功解析源代码。
/* Global variables holding parse results for entrypoints. */
Proc YY_RESULT_Proc_ = 0;
// ...
/* Entrypoint: parse Proc from string. */
Proc psProc(const char *str)
{
YY_BUFFER_STATE buf;
mylang__init_lexer(0);
buf = mylang__scan_string(str);
int result = yyparse();
mylang__delete_buffer(buf);
if (result)
{ /* Failure */
return 0;
}
else
{ /* Success */
return YY_RESULT_Proc_;
}
}
struct Proc_;
typedef struct Proc_ *Proc;
struct Proc_
{
enum { is_PGround, is_PCollect, is_PVar, is_PVarRef, is_PNil, is_PSimpleType, is_PNegation, is_PConjunction, is_PDisjunction, is_PEval, is_PMethod, is_PExprs, is_PNot, is_PNeg, is_PMult, is_PDiv, is_PMod, is_PPercentPercent, is_PAdd, is_PMinus, is_PPlusPlus, is_PMinusMinus, is_PLt, is_PLte, is_PGt, is_PGte, is_PMatches, is_PEq, is_PNeq, is_PAnd, is_POr, is_PSend, is_PContr, is_PInput, is_PChoice, is_PMatch, is_PBundle, is_PIf, is_PIfElse, is_PNew, is_PPar } kind;
union
{
struct { Ground ground_; } pground_;
struct { Collection collection_; } pcollect_;
struct { ProcVar procvar_; } pvar_;
struct { Var var_; VarRefKind varrefkind_; } pvarref_;
struct { SimpleType simpletype_; } psimpletype_;
struct { Proc proc_; } pnegation_;
struct { Proc proc_1, proc_2; } pconjunction_;
struct { Proc proc_1, proc_2; } pdisjunction_;
struct { Name name_; } peval_;
struct { ListProc listproc_; Proc proc_; Var var_; } pmethod_;
struct { Proc proc_; } pexprs_;
struct { Proc proc_; } pnot_;
struct { Proc proc_; } pneg_;
struct { Proc proc_1, proc_2; } pmult_;
struct { Proc proc_1, proc_2; } pdiv_;
struct { Proc proc_1, proc_2; } pmod_;
struct { Proc proc_1, proc_2; } ppercentpercent_;
struct { Proc proc_1, proc_2; } padd_;
struct { Proc proc_1, proc_2; } pminus_;
struct { Proc proc_1, proc_2; } pplusplus_;
struct { Proc proc_1, proc_2; } pminusminus_;
struct { Proc proc_1, proc_2; } plt_;
struct { Proc proc_1, proc_2; } plte_;
struct { Proc proc_1, proc_2; } pgt_;
struct { Proc proc_1, proc_2; } pgte_;
struct { Proc proc_1, proc_2; } pmatches_;
struct { Proc proc_1, proc_2; } peq_;
struct { Proc proc_1, proc_2; } pneq_;
struct { Proc proc_1, proc_2; } pand_;
struct { Proc proc_1, proc_2; } por_;
struct { ListProc listproc_; Name name_; Send send_; } psend_;
struct { ListName listname_; Name name_; NameRemainder nameremainder_; Proc proc_; } pcontr_;
struct { Proc proc_; Receipt receipt_; } pinput_;
struct { ListBranch listbranch_; } pchoice_;
struct { ListCase listcase_; Proc proc_; } pmatch_;
struct { Bundle bundle_; Proc proc_; } pbundle_;
struct { Proc proc_1, proc_2; } pif_;
struct { Proc proc_1, proc_2, proc_3; } pifelse_;
struct { ListNameDecl listnamedecl_; Proc proc_; } pnew_;
struct { Proc proc_1, proc_2; } ppar_;
} u;
};
我有几个关于 Proc psProc(const char *str)
的问题。
我可以在
psProc
returns 之后立即释放char *str
参数引用的源缓冲区吗?我猜返回的Proc
可能包含指向输入源缓冲区的指针,所以我应该确保源缓冲区的生命周期比返回的指针长。对吗?我应该如何释放返回的
Proc
?返回的Proc
是一个指向Proc_
的指针,由指针组成一个抽象语法树。我只需要在返回的指针上调用一次free()
就可以释放,对吗?在
Proc psProc(const char *str)
的方法体中,returns一个指针存储在全局变量YY_RESULT_Proc_
中。这是否意味着我不能从不同线程同时调用psProc
?
此类问题应在该工具的文档中得到解答。但是我在那里找不到它们:-(我也没有太多使用 BNFC 的经验,所以在应用这个答案时要谨慎。
时,您当然可以释放字符串psProc
调用词法分析器的scan_string
接口,该接口复制提供的字符串。 Flex 喜欢在标记化时修改输入,因此它只能通过复制来处理const char*
个输入。因此,字符串可以在调用scan_string
后立即释放,但psProc
在返回之前解析整个输入,因此您无论如何都不必这样做。当psProc
returns.我怀疑这对你来说是否是个问题,但如果你计划解析非常大的内存中字符串,你可能需要考虑使用
fmemopen
(至少,在 Posix平台)将字符串打开为FILE*
。这并没有避免复制,但它以 8k 左右的块进行复制,这避免了在解析期间保留整个字符串的两个副本。我不知道 BNFC 如何期望您释放解析树节点。 (事实上,我宁愿怀疑它不希望你那样做。)节点与内部指针链接,当然可以编写一个 AST walker,它使用 [= 递归地释放所有节点42=]-顺序遍历。但是我没有看到任何生成的代码可以做到这一点。可能是我看的不够仔细
在顶级节点上调用
free()
只会释放一个节点。树的其余部分将被泄露,因为不存在指向它的其他指针。我很确定您对线程安全的怀疑是正确的。全局由
Proc
生产的缩减操作分配给,然后由psProc
返回。没有锁定,所以如果另一个线程中有另一个解析器,则全局可能会被覆盖。全局只是指向要返回的节点的指针,节点本身应该是线程安全的,因为它是由解析器线程动态分配的。因此,您可能会更改全局的声明以使用线程本地存储,但这必须通过 post 处理生成的代码来完成。