c - 对应于模式的动作中的字符串声明会影响对应于模式的下一个动作
问题描述
我在与模式匹配时从输入文件中提取标记的名称,随后使用函数"<"([a-z][a-z]*)
提取仅包含字符串(字符数组)中名称部分的子字符串。tagName
strncpy
大多数似乎运行良好,当 first<div
匹配时,除了tagName
其中获得的提取子字符串的长度strncpy(tagName, yytext + 1, yytextlen - 1);
应该是 3 的div,而是得到 6 。
在 input 的第二次匹配期间,在第一次匹配期间<a
的值tagName
在这里持续存在并影响输出。
我无法理解这种行为的原因。
预期输出:
tagName: 0\y�� /* Garbage value */, tagName length: 6 /* Why is this 6 */
An html tag { yytext: <a, yyleng: 2, text: a, len: 1 }
电流输出:请参阅下面的输出部分。
我在 Ubuntu 16 上使用 flex 版本 2.6.0 和 gcc 编译器。
.lex 文件:
HTML_TAG [a-z][a-z]*
REACT_TAG [A-Z][[:alnum:]]*
%%
"<"{HTML_TAG} {
int yytextlen = yyleng;
char tagName[yytextlen - 1];
printf("tagName: %s, tagName length: %lu\n", tagName, strlen(tagName));
strncpy(tagName, yytext + 1, yytextlen - 1);
printf("An html tag { yytext: %s, yyleng: %zu, text: %s, len: %lu }\n", yytext, yyleng, tagName, strlen(tagName));
printf("\n\n");
}
"<"{REACT_TAG} {
printf("A react tag { text: %s, len: %zu }\n", yytext, yyleng);
printf("\n\n");
}
"</"{HTML_TAG} {
printf("A closing html tag { text: %s, len: %zu }\n", yytext, yyleng);
printf("\n\n");
}
"</"{REACT_TAG} {
printf("A closing react tag { text: %s, len: %zu }\n", yytext, yyleng);
printf("\n\n");
}
[ \t\n] /* eat up whitespace */
. ;
%%
int main(int argc, char **argv)
{
++argv, --argc; /* skip over program name */
if (argc > 0)
yyin = fopen(argv[0], "r");
else
yyin = stdin;
yylex();
}
输入文件:
<div
attribute1=""
attribute2=""
attribute3=""
>
<a>
Text Content
</a>
<ReactTag attribute4="" />
<ReactTag2 attribute5="">
Text Content
</ReactTag2>
</div>
输出:
tagName: 0\y��, tagName length: 6
An html tag { yytext: <div, yyleng: 4, text: div��, len: 6 }
tagName: div��, tagName length: 6
An html tag { yytext: <a, yyleng: 2, text: aiv��, len: 6 }
An closing html tag { text: </a, len: 3 }
A react tag { text: <ReactTag, len: 9 }
A react tag { text: <ReactTag2, len: 10 }
An closing react tag { text: </ReactTag2, len: 11 }
An closing html tag { text: </div, len: 5 }
解决方案
我只看了你指示的动作。如果你在其他地方犯了同样的错误,我相信你能找到它们。
这是"<"{HTML_TAG}
行动,我的评论:
{
int yytextlen = yyleng;
这个变量有什么意义?yyleng
在执行此操作期间不会更改其值。就用它吧。
char tagName[yytextlen - 1];
您想保存一个带有yyleng - 1
字符(因为yyleng
包括<
.)的标记名,这意味着您需要一个大小为yyleng - 1 + 1
(或yyleng
简称为)的临时字符串,因为您需要 NUL 终止副本。除非你真的不需要这个副本。但我们稍后再谈。
printf("tagName: %s, tagName length: %lu\n", tagName, strlen(tagName));
我知道您打算复制yytext
到tagName
,但您还没有完成。所以此时它是未初始化的存储。试图打印它是未定义的行为。试图得到它的长度strlen
是未定义的行为。显然,这将打印垃圾。(无论如何,你为什么需要计算 strlen
?你知道这个字符串到底有多长:yyleng - 1
。)
strncpy(tagName, yytext + 1, yytextlen - 1);
在某些时候我会放弃这个论点,但这很好地说明了为什么你不应该使用strncpy
(除了在它设计的罕见用例中:不需要 NUL 的固定长度数据库字段终止)。人们似乎认为那样strncpy
“更安全”,strcpy
因为它的n
名字中有一个。但它实际上是极其不安全的,甚至比 更不安全strcpy
,因为它不会以 NUL 终止副本。正如我们在上面看到的,您也没有为 NUL 终止符留出足够的空间,所以如果您使用了strcpy
NUL 终止符,那么它会将 NUL 终止符写入缓冲区之外。但是,如果您使缓冲区足够大,那strcpy
将是完全正确的。
此外,如果 a 中的源字符串strncpy
比目标字符串短,strncpy
则用 NUL 填充目标的其余部分。所有的。这几乎总是浪费循环(除了在这种情况下,它根本不写入 NUL 并产生未定义的行为)。
如果您真的想复制限制为最大长度的字符串,请使用strndup
(如果您的 C 库包含它,现在大多数情况下都会这样做)。strndup
复制具有长度限制的字符串。它以 NUL 终止副本。并且它动态地为副本分配足够的空间。如果您想要一个安全的界面,那就是可以使用的界面。
但是为什么你觉得你需要在这里复制呢?如果您打算将令牌传递给解析器,那么您确实需要一个副本,但本地可变长度数组不是您需要的副本,因为本地数组的生命周期在操作后立即结束终止,这在副本可以使用之前很久。如果确实需要副本,则需要动态分配的副本。而这正是strndup
给你的。
printf("An html tag { yytext: %s, yyleng: %zu, text: %s, len: %lu }\n",
yytext, yyleng, tagName, strlen(tagName));
所以现在你已经制作了你的副本。但是你用一个不以 NUL 终止的库函数来实现它,所以它仍然是未定义的行为,就像它是一个字符串一样使用副本。如果它是 NUL 终止的,它只会是一个字符串。并且将副本传递给它仍然是未定义的行为,strlen
就好像它是一个字符串一样。
另一方面,打印出来yytext
就好yyleng
了。
printf("\n\n");
}
至此,动作结束。tagName
不复存在。它的生命已经走到了尽头。yytext
仍然可以,但不会太久:一旦扫描程序开始寻找下一个令牌(这是马上的,因为您的操作不是return
其调用者的令牌),它将收回控制权yytext
,以不可预知的方式修改其内容方法。因此,如果您需要字符串的副本以返回令牌类型,则您必须已经制作了一个仍然存在的副本。
希望对大家有所帮助。
推荐阅读
- angular - 角度不使用 sass/scss 编译
- ruby-on-rails - 关联模型自动更新?
- django - 当模型具有一对多自引用关系时如何使用 TastyPie 创建记录?
- c# - 尝试通过 C# 应用程序访问 SAP 服务层时出现 500 错误
- python - Python 多处理 - apply_async 不起作用
- c++ - 输入是如何存储的?
- excel - Excel 用户窗体多行文本框截断文本
- ios - 架构 x86_64 clang 的 PaymentSDK':错误:链接器命令失败,退出代码为 1(使用 -v 查看调用)
- laravel - 如何在 laravel 中为每个用户设置动态权限授予
- python - 如何从左到右运行非贪婪的正则表达式