模式对应的动作中的字符串声明会影响模式对应的下一个动作

string declaration in action corresponding to a pattern affects next action corresponding to pattern

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

大多数似乎工作正常,当第一个 <div 被匹配时,除了 tagName 的长度是 strncpy(tagName, yytext + 1, yytextlen - 1); 中获得的提取子字符串应该是 3 对于 div,而是得到 6。

在输入 <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 是 "safer" 而不是 strcpy 因为它的名称中有一个 n。但它实际上非常不安全,甚至比 strcpy 还不安全,因为 它不会以 NUL 终止副本 。正如我们在上面看到的,您也没有为 NUL 终止符留下足够的 space,因此如果您使用了 strcpy,它确实 NUL 终止,那么它将在 NUL 终止符之外写入缓冲区。但是,如果您将缓冲区设置得足够大,strcpy 就会完全正确。

此外,如果 strncpy 中的源字符串比目标短,strncpy 会用 NUL 填充目标的其余部分。所有的。这几乎总是浪费周期(除了在这种情况下,它根本不写入 NUL 并产生未定义的行为)。

如果您真的想复制一个限制在最大长度内的字符串,请使用 strndup(如果您的 C 库包含它,现在大多数都这样做)。 strndup 复制有长度限制的字符串。 并且它以NUL-终止副本。 并且 它动态地为副本分配足够的space。如果您想要一个安全的界面,那就是那个界面。

但是你为什么觉得这里需要复印呢?如果您打算将令牌传递给解析器,那么您确实需要一个副本,但本地可变长度数组不是您需要的副本,因为本地数组的生命周期在操作结束后立即结束在可以使用副本之前很久就终止了。如果您确实需要一个副本,您将需要一个动态分配的副本。而这正是 strndup 会给你的。

  printf("An html tag { yytext: %s, yyleng: %zu, text: %s, len: %lu }\n",
                        yytext, yyleng, tagName, strlen(tagName));

现在你已经完成了你的副本。但是你用一个不以 NUL 终止的库函数来创建它,所以使用副本仍然是未定义的行为,就好像它是一个字符串一样。如果它以 NUL 结尾,它只会是一个字符串。将副本传递给 strlen 仍然是未定义行为,就好像它是一个字符串一样。

另一方面,打印出 yytextyyleng 就好了。

  printf("\n\n");
}

至此,动作结束。 tagName 已不存在。它的生命已经走到了尽头。 yytext 仍然可以,但时间不会太长:一旦扫描器开始寻找下一个令牌(这是马上,因为您的操作没有 return 其调用者的令牌),它将收回 yytext 的控制权,以不可预知的方式修改其内容。因此,如果您需要使用令牌类型将字符串副本复制到 return,则您必须已经制作了一个仍然有效的副本。

希望对大家有所帮助。