首页 > 解决方案 > 对应于模式的动作中的字符串声明会影响对应于模式的下一个动作

问题描述

我在与模式匹配时从输入文件中提取标记的名称,随后使用函数"<"([a-z][a-z]*)提取仅包含字符串(字符数组)中名称部分的子字符串。tagNamestrncpy

大多数似乎运行良好,当 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 }

标签: ccompiler-constructionflex-lexer

解决方案


我只看了你指示的动作。如果你在其他地方犯了同样的错误,我相信你能找到它们。

这是"<"{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));

我知道您打算复制yytexttagName,但您还没有完成。所以此时它是未初始化的存储。试图打印它是未定义的行为。试图得到它的长度strlen是未定义的行为。显然,这将打印垃圾。(无论如何,你为什么需要计算 strlen?你知道这个字符串到底有多长:yyleng - 1。)

  strncpy(tagName, yytext + 1, yytextlen - 1);

在某些时候我会放弃这个论点,但这很好地说明了为什么你不应该使用strncpy(除了在它设计的罕见用例中:不需要 NUL 的固定长度数据库字段终止)。人们似乎认为那样strncpy“更安全”,strcpy因为它的n名字中有一个。但它实际上是极其不安全的,甚至比 更不安全strcpy,因为它不会以 NUL 终止副本。正如我们在上面看到的,您也没有为 NUL 终止符留出足够的空间,所以如果您使用了strcpyNUL 终止符,那么它会将 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,以不可预知的方式修改其内容方法。因此,如果您需要字符串的副本以返回令牌类型,则您必须已经制作了一个仍然存在的副本。

希望对大家有所帮助。


推荐阅读