如何使用 PackCC 解析器返回的“$$”值?
How to use the "$$" value returned by a PackCC parser?
这是一个最小的 PackCC 语法示例。
我尝试在解析后检索并打印 $$
值。单词匹配,但 printf
调用仅显示垃圾。
%value "char*"
word <- < [a-z]+[\n]* > {$$ = ;}
%%
int main(void)
{
char* val = "Value";
// Create a file to parse.
FILE* f = freopen("text.txt", "w", stdin);
if(f != NULL) {
// Write the text to parse.
fprintf(f, "example\n");
// Set the file in read mode.
f = freopen("text.txt", "r", stdin);
pcc_context_t *ctx = pcc_create(NULL);
// I expect val to receive the "$$" value from the parse.
while(pcc_parse(ctx, &val));
printf("val: %s\n",val);
pcc_destroy(ctx);
fclose(f);
}
else {
puts("File is NULL");
}
return 0;
}
PackCC 文档说 $$
是:
The output variable, to which the result of the rule is stored.
它说 pcc_parse
函数:
Parses an input text (from standard input by default) and returns the result in ret
. The ret
can be NULL
if no output data is needed. This function returns 0 if no text is left to be parsed, or a non-0 value otherwise.
您使用 $$
没有问题,因为 word
操作存储在 $$
中的 char *
值忠实地返回到 val
.
问题是 char*
值是一个指向动态分配内存的指针,而到解析器 returns 时,动态分配的内存已经被释放。所以返回到 val
的指针是一个悬空指针,并且在调用 printf
时,内存区域已被其他对象使用。
PackCC 的文档(例如它)没有详细介绍其内存管理策略,因此并不清楚 </code> 指针在规则中的有效期。我认为假设它仅在规则中的最后一个操作结束之前有效是最安全的。但是假设指针将比对 <code>pcc_parse
的调用更持久当然是不合理的。毕竟,解析器无法知道您已将指针存储在解析器上下文之外。解析器不能依赖程序员 free
捕获规则期间产生的字符串;必须 free
每次捕获,即使是从未使用过的捕获,也会带来极大的不便。为了避免内存泄漏,解析器因此必须 free
它的捕获缓冲区。
如果您能够使用 valgrind 或一些类似的工具,问题很容易看出。 (Valgrind 适用于大多数 Linux 发行版和 OS X 自 v10 以来。9.x。可能支持其他平台。)运行 您在 valgrind 下的解析器产生了以下错误报告(截断):
$ valgrind --leak-check=full ./test3
==2763== Memcheck, a memory error detector
==2763== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2763== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==2763== Command: ./test3
==2763==
==2763== Invalid read of size 1
==2763== at 0x4C34CF2: strlen (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2763== by 0x4E9B5D2: vfprintf (vfprintf.c:1643)
==2763== by 0x4F7017B: __printf_chk (printf_chk.c:35)
==2763== by 0x10A32D: printf (stdio2.h:104)
==2763== by 0x10A32D: main (test3.c:1013)
==2763== Address 0x5232e20 is 0 bytes inside a block of size 9 free'd
==2763== at 0x4C32D3B: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2763== by 0x109498: pcc_capture_table__term (test3.c:339)
==2763== by 0x1096E3: pcc_thunk_chunk__destroy (test3.c:441)
==2763== by 0x10974F: pcc_lr_answer__destroy (test3.c:557)
==2763== by 0x109818: pcc_lr_memo_map__term (test3.c:602)
==2763== by 0x10985F: pcc_lr_table_entry__destroy (test3.c:619)
==2763== by 0x109BB8: pcc_lr_table__shift (test3.c:680)
==2763== by 0x109C1C: pcc_commit_buffer (test3.c:757)
==2763== by 0x10A22C: pcc_parse (test3.c:986)
==2763== by 0x10A314: main (test3.c:1011)
==2763== Block was alloc'd at
==2763== at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2763== by 0x108C9D: pcc_malloc_e (test3.c:225)
==2763== by 0x108FF3: pcc_strndup_e (test3.c:252)
==2763== by 0x109038: pcc_get_capture_string (test3.c:764)
==2763== by 0x10904E: pcc_action_word_0 (test3.c:892)
==2763== by 0x108C56: pcc_do_action (test3.c:872)
==2763== by 0x108C87: pcc_do_action (test3.c:875)
==2763== by 0x10A224: pcc_parse (test3.c:983)
==2763== by 0x10A314: main (test3.c:1011)
要完成的事情很多,但它表明有人试图使用已经释放的 9 字节动态分配内存区域的第一个字节。 (“地址 0x5232e20 是大小为 9 的块内的 0 个字节”。)此外,回溯显示错误是由对 strlen
的调用触发的,printf
已调用该调用; printf
是从您的 main
函数调用的。 (不幸的是,PackCC 没有发布 #line
指令,因此无法将生成的 C 解析器中的行号与原始 PEG 语法文件中的行号相关联。但是,在这种情况下,很明显 printf
是,因为在 main
函数中实际上只有一种可能性。)Valgrind 还向您显示动态分配内存的位置;尽管您必须手边有生成的解析器的副本才能查看所有部分如何组合在一起,但调用跟踪中的函数名称还是有些帮助的。
解决方案与您必须在依赖于 (f)lex-based 扫描器的解析器中处理 yytext
的方式基本相同:因为操作指向的字符串在内存中,其生命周期即将结束,必须复制您以后要使用的任何令牌。最简单的方法是使用 strdup
(或等效方法,如果您不能使用标准 Posix 接口),将操作更改为:
word <- < [a-z]+[\n]* > {$$ = strdup();}
执行此操作后,“单词”example
将按预期打印(包括终止它的换行符)。
您还必须记住 free
您所做的副本以避免内存泄漏。 Valgrind 还将帮助您检测内存泄漏,因此它可以帮助您捕获因忘记这样做而导致的错误。
这是一个最小的 PackCC 语法示例。
我尝试在解析后检索并打印 $$
值。单词匹配,但 printf
调用仅显示垃圾。
%value "char*"
word <- < [a-z]+[\n]* > {$$ = ;}
%%
int main(void)
{
char* val = "Value";
// Create a file to parse.
FILE* f = freopen("text.txt", "w", stdin);
if(f != NULL) {
// Write the text to parse.
fprintf(f, "example\n");
// Set the file in read mode.
f = freopen("text.txt", "r", stdin);
pcc_context_t *ctx = pcc_create(NULL);
// I expect val to receive the "$$" value from the parse.
while(pcc_parse(ctx, &val));
printf("val: %s\n",val);
pcc_destroy(ctx);
fclose(f);
}
else {
puts("File is NULL");
}
return 0;
}
PackCC 文档说 $$
是:
The output variable, to which the result of the rule is stored.
它说 pcc_parse
函数:
Parses an input text (from standard input by default) and returns the result in
ret
. Theret
can beNULL
if no output data is needed. This function returns 0 if no text is left to be parsed, or a non-0 value otherwise.
您使用 $$
没有问题,因为 word
操作存储在 $$
中的 char *
值忠实地返回到 val
.
问题是 char*
值是一个指向动态分配内存的指针,而到解析器 returns 时,动态分配的内存已经被释放。所以返回到 val
的指针是一个悬空指针,并且在调用 printf
时,内存区域已被其他对象使用。
PackCC 的文档(例如它)没有详细介绍其内存管理策略,因此并不清楚 </code> 指针在规则中的有效期。我认为假设它仅在规则中的最后一个操作结束之前有效是最安全的。但是假设指针将比对 <code>pcc_parse
的调用更持久当然是不合理的。毕竟,解析器无法知道您已将指针存储在解析器上下文之外。解析器不能依赖程序员 free
捕获规则期间产生的字符串;必须 free
每次捕获,即使是从未使用过的捕获,也会带来极大的不便。为了避免内存泄漏,解析器因此必须 free
它的捕获缓冲区。
如果您能够使用 valgrind 或一些类似的工具,问题很容易看出。 (Valgrind 适用于大多数 Linux 发行版和 OS X 自 v10 以来。9.x。可能支持其他平台。)运行 您在 valgrind 下的解析器产生了以下错误报告(截断):
$ valgrind --leak-check=full ./test3
==2763== Memcheck, a memory error detector
==2763== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2763== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==2763== Command: ./test3
==2763==
==2763== Invalid read of size 1
==2763== at 0x4C34CF2: strlen (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2763== by 0x4E9B5D2: vfprintf (vfprintf.c:1643)
==2763== by 0x4F7017B: __printf_chk (printf_chk.c:35)
==2763== by 0x10A32D: printf (stdio2.h:104)
==2763== by 0x10A32D: main (test3.c:1013)
==2763== Address 0x5232e20 is 0 bytes inside a block of size 9 free'd
==2763== at 0x4C32D3B: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2763== by 0x109498: pcc_capture_table__term (test3.c:339)
==2763== by 0x1096E3: pcc_thunk_chunk__destroy (test3.c:441)
==2763== by 0x10974F: pcc_lr_answer__destroy (test3.c:557)
==2763== by 0x109818: pcc_lr_memo_map__term (test3.c:602)
==2763== by 0x10985F: pcc_lr_table_entry__destroy (test3.c:619)
==2763== by 0x109BB8: pcc_lr_table__shift (test3.c:680)
==2763== by 0x109C1C: pcc_commit_buffer (test3.c:757)
==2763== by 0x10A22C: pcc_parse (test3.c:986)
==2763== by 0x10A314: main (test3.c:1011)
==2763== Block was alloc'd at
==2763== at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2763== by 0x108C9D: pcc_malloc_e (test3.c:225)
==2763== by 0x108FF3: pcc_strndup_e (test3.c:252)
==2763== by 0x109038: pcc_get_capture_string (test3.c:764)
==2763== by 0x10904E: pcc_action_word_0 (test3.c:892)
==2763== by 0x108C56: pcc_do_action (test3.c:872)
==2763== by 0x108C87: pcc_do_action (test3.c:875)
==2763== by 0x10A224: pcc_parse (test3.c:983)
==2763== by 0x10A314: main (test3.c:1011)
要完成的事情很多,但它表明有人试图使用已经释放的 9 字节动态分配内存区域的第一个字节。 (“地址 0x5232e20 是大小为 9 的块内的 0 个字节”。)此外,回溯显示错误是由对 strlen
的调用触发的,printf
已调用该调用; printf
是从您的 main
函数调用的。 (不幸的是,PackCC 没有发布 #line
指令,因此无法将生成的 C 解析器中的行号与原始 PEG 语法文件中的行号相关联。但是,在这种情况下,很明显 printf
是,因为在 main
函数中实际上只有一种可能性。)Valgrind 还向您显示动态分配内存的位置;尽管您必须手边有生成的解析器的副本才能查看所有部分如何组合在一起,但调用跟踪中的函数名称还是有些帮助的。
解决方案与您必须在依赖于 (f)lex-based 扫描器的解析器中处理 yytext
的方式基本相同:因为操作指向的字符串在内存中,其生命周期即将结束,必须复制您以后要使用的任何令牌。最简单的方法是使用 strdup
(或等效方法,如果您不能使用标准 Posix 接口),将操作更改为:
word <- < [a-z]+[\n]* > {$$ = strdup();}
执行此操作后,“单词”example
将按预期打印(包括终止它的换行符)。
您还必须记住 free
您所做的副本以避免内存泄漏。 Valgrind 还将帮助您检测内存泄漏,因此它可以帮助您捕获因忘记这样做而导致的错误。