Bison:$1、$2 等的交替顺序给出错误的输出
Bison: alternate order of $1, $2 etc gives wrong output
我正在使用 bison 构建用于教育目的的解析器。这是我非常简单的语法:
program: KW_VAR ident {printf("var %s\n", );} ;
ident:
| IDENTIFIER OP_PLUS IDENTIFIER {sprintf($$, "%s + %s\n", , );}
;
其中 KW_VAR 表示单词 'var',OP_PLUS 表示运算符“+”。
var hello + hi 是这个语法可接受的短语。所以当我使用上面的代码时,一切正常并且 printf 给出: var hello + hi 正如预期的那样。但是当我尝试更改 sprintf 中 $1、$3 的顺序时,如下所示,printf 给出:var hi + hi +。我期望的是 var hi + hello.
program: KW_VAR ident {printf("var %s\n", );} ;
ident:
| IDENTIFIER OP_PLUS IDENTIFIER {sprintf($$, "%s + %s\n", , );}
;
为什么会这样?我的代码有问题吗?
让我们考虑以下代码:
const char* greeting = "Hello";
const char* greeted = "world";
char* message;
sprintf(message, "%s, %s!", greeting, greeted);
有效吗? No! message
从未初始化,所以它指向外部 space。您当然不能将它传递给 sprintf
并期望一切顺利。
那么我们对以下内容有何期待?
sprintf($$, "%s + %s\n", , );
我们还没有初始化 $$
,所以我们再次套印了一些随机内存。除了在这种情况下,它不是完全随机的,因为就在 bison-generated 解析器执行任何操作之前,它首先执行此操作:
$$ = ;
如此有效,sprintf
调用是:
sprintf(, "%s + %s\n", , );
这是另一种形式的未定义行为。在 Ubuntu 系统上引用 man 3 sprintf
,
the standards explicitly note that the results are undefined if source and destination buffers overlap when calling sprintf()
联机帮助页指出,尽管标准不允许,但对于某些 gcc 和 glibc 版本,如果源缓冲区覆盖自身(作为附加到缓冲区的一种方式),它似乎可以工作。
当然,这假设指向 </code> 的字符串中实际上有足够的 space 来保存 <code>sprintf
的结果。在那儿?谁知道?我们看不到 </code> 来自哪里。</p>
<p>语义值 <code>
已由词法扫描器填充。在扫描仪中执行此操作的正确方法如下所示(尽管真实模式可能包含下划线):
[[:alpha:]][[:alnum:]]* { yylval = strdup(yytext); return IDENTIFIER; }
在那种情况下,语义值 </code> 将不够长,因为它与复制标识符所需的长度一样长,不再需要。即使<code>sprintf
"seems to work"也会缓冲区溢出,随机内存会被覆盖。 [注1]
那么,怎么办?简单的解决方案(如果有的话)是使用 asprintf
,它类似于 sprintf
,只是它分配了一个新的缓冲区。使用该功能,您可以编写野牛动作:
asprintf(&$$, "%s + %s\n", , );
(注意 &
:asprintf
需要一个指向 char*
的指针,并且 returns 分配的内存地址进入 pointed-to 参数。所以在此调用结束时,$$
将指向具有正确字符串的 newly-allocated 缓冲区。)
如果您的系统没有 asprintf
,或者您想为没有的系统做好准备,请查看 中 concatf
的实现
备注
很多时候,你会在同学们写的词法扫描器中看到如下内容:
[[:alpha:]][[:alnum:]]* { yylval = yytext; /* DON'T DO THIS!!! */
return IDENTIFIER;
}
这是不正确的,因为 yytext
指向一个属于词法扫描器本身的临时缓冲区。不能保证当解析器开始查看指针时,它仍然指向相同的数据。或者,事实上,在任何事情上;扫描器很可能会释放该缓冲区并开始使用另一个缓冲区。所以这已经是个问题了。如果这还不够,sprintf
将覆盖 扫描器的输入缓冲区 ,这可能会在读取下一个标记时产生有趣的结果。
我正在使用 bison 构建用于教育目的的解析器。这是我非常简单的语法:
program: KW_VAR ident {printf("var %s\n", );} ;
ident:
| IDENTIFIER OP_PLUS IDENTIFIER {sprintf($$, "%s + %s\n", , );}
;
其中 KW_VAR 表示单词 'var',OP_PLUS 表示运算符“+”。
var hello + hi 是这个语法可接受的短语。所以当我使用上面的代码时,一切正常并且 printf 给出: var hello + hi 正如预期的那样。但是当我尝试更改 sprintf 中 $1、$3 的顺序时,如下所示,printf 给出:var hi + hi +。我期望的是 var hi + hello.
program: KW_VAR ident {printf("var %s\n", );} ;
ident:
| IDENTIFIER OP_PLUS IDENTIFIER {sprintf($$, "%s + %s\n", , );}
;
为什么会这样?我的代码有问题吗?
让我们考虑以下代码:
const char* greeting = "Hello";
const char* greeted = "world";
char* message;
sprintf(message, "%s, %s!", greeting, greeted);
有效吗? No! message
从未初始化,所以它指向外部 space。您当然不能将它传递给 sprintf
并期望一切顺利。
那么我们对以下内容有何期待?
sprintf($$, "%s + %s\n", , );
我们还没有初始化 $$
,所以我们再次套印了一些随机内存。除了在这种情况下,它不是完全随机的,因为就在 bison-generated 解析器执行任何操作之前,它首先执行此操作:
$$ = ;
如此有效,sprintf
调用是:
sprintf(, "%s + %s\n", , );
这是另一种形式的未定义行为。在 Ubuntu 系统上引用 man 3 sprintf
,
the standards explicitly note that the results are undefined if source and destination buffers overlap when calling
sprintf()
联机帮助页指出,尽管标准不允许,但对于某些 gcc 和 glibc 版本,如果源缓冲区覆盖自身(作为附加到缓冲区的一种方式),它似乎可以工作。
当然,这假设指向 </code> 的字符串中实际上有足够的 space 来保存 <code>sprintf
的结果。在那儿?谁知道?我们看不到 </code> 来自哪里。</p>
<p>语义值 <code>
已由词法扫描器填充。在扫描仪中执行此操作的正确方法如下所示(尽管真实模式可能包含下划线):
[[:alpha:]][[:alnum:]]* { yylval = strdup(yytext); return IDENTIFIER; }
在那种情况下,语义值 </code> 将不够长,因为它与复制标识符所需的长度一样长,不再需要。即使<code>sprintf
"seems to work"也会缓冲区溢出,随机内存会被覆盖。 [注1]
那么,怎么办?简单的解决方案(如果有的话)是使用 asprintf
,它类似于 sprintf
,只是它分配了一个新的缓冲区。使用该功能,您可以编写野牛动作:
asprintf(&$$, "%s + %s\n", , );
(注意 &
:asprintf
需要一个指向 char*
的指针,并且 returns 分配的内存地址进入 pointed-to 参数。所以在此调用结束时,$$
将指向具有正确字符串的 newly-allocated 缓冲区。)
如果您的系统没有 asprintf
,或者您想为没有的系统做好准备,请查看 concatf
的实现
备注
很多时候,你会在同学们写的词法扫描器中看到如下内容:
[[:alpha:]][[:alnum:]]* { yylval = yytext; /* DON'T DO THIS!!! */ return IDENTIFIER; }
这是不正确的,因为
yytext
指向一个属于词法扫描器本身的临时缓冲区。不能保证当解析器开始查看指针时,它仍然指向相同的数据。或者,事实上,在任何事情上;扫描器很可能会释放该缓冲区并开始使用另一个缓冲区。所以这已经是个问题了。如果这还不够,sprintf
将覆盖 扫描器的输入缓冲区 ,这可能会在读取下一个标记时产生有趣的结果。