繁体   English   中英

在C中处理长递归生产时如何防止堆栈溢出?

[英]How to prevent stack overflow when dealing with long recursive productions in C?

给定一种语法,如何在C中计算FIRST和FOLLOW集时如何避免堆栈溢出问题。当我不得不通过长时间的生产递归时,该问题就出现在我的代码中。

例:

S->ABCD
A->aBc | epsilon
B->Bc
C->a | epsilon
D->B

那只是语法上的麻烦。 递归是这样的:

S->A
C->A
A->B
B->D
D->aBc | epsilon
 FIRST(S)=FIRST(A)=FIRST(B)=FIRST(D)={a,epsilon}. 

提供一个C(不是C ++)代码来计算并打印上面的语法的FIRST和FOLLOW集,请记住,您可能会遇到一个较长的语法,该语法具有特定非终结点的多个隐式的first / follow集。

例如:

FIRST(A)=FIRST(B)=FIRST(B)=FIRST(C)=FIRST(D)=FIRST(E)=FIRST(F)=FIRST(G)=FIRST(H)=FIRST(I)=FIRST(J)=FIRST(K)={k,l,epsilon}.

也就是说:要获得FIRST(A)您必须计算FIRST(B) ,依此类推,直到到达FIRST(K) ,其FIRST(K)端子为'k''l'epsilon 隐含的时间越长,由于多次递归而导致堆栈溢出的可能性就越大。
如何用C语言避免这种情况,但仍能获得正确的输出?
用C(不是C ++)代码解释。

char*first(int i)
{
    int j,k=0,x;
    char temp[500], *str;
    for(j=0;grammar[i][j]!=NULL;j++)
    {
        if(islower(grammar[i][j][0]) || grammar[i][j][0]=='#' || grammar[i][j][0]==' ')
        {
           temp[k]=grammar[i][j][0];
           temp[k+1]='\0';
        }
        else
        {
            if(grammar[i][j][0]==terminals[i])
            {
                temp[k]=' ';
                temp[k+1]='\0';
            }
            else
            {
                x=hashValue(grammar[i][j][0]);
                str=first(x);
                strncat(temp,str,strlen(str));
            }
        }
        k++;
    }
    return temp;
}

我的代码进入堆栈溢出。 我该如何避免呢?

您的程序溢出堆栈不是因为语法“太复杂”,而是因为它是左递归的。 由于您的程序不会检查是否已经通过非终端递归,因此一旦尝试计算first('B') ,它将进入无限递归,最终将填充调用堆栈。 (在示例语法中, B不仅是左递归的,而且也没有用,因为它没有非递归的产生,这意味着它永远不会派生仅包含终端的句子。)

不过,这不是唯一的问题。 该程序还存在至少两个其他缺陷:

  • 在将终端添加到非终端的FIRST集中之前,它不会检查给定的终端是否已添加到该FIRST集合中。 因此,在第一组中将有重复的端子。

  • 该程序仅检查右侧的第一个符号。 但是,如果非终结符可以产生ε(换句话说,非终结符可以为null ),则还需要使用以下符号来计算FIRST集。

    例如,

     A → BC d B → b | ε C → c | ε 

    此处, 第一A )为{b, c, d} (并且类似地, FOLLOWB )是{c, d} 。)

递归对FIRSTFOLLOW集的计算没有太大帮助。 描述最简单的算法就是该算法,类似于《 龙书》中介绍的算法,该算法可以满足任何实用的语法要求:

  1. 对于每个非终端,计算它是否可为空。

  2. 使用上面的,对于每一个非末端N到该组的每个生产对于N 主导符号初始化FIRST(N)。 如果符号是右侧的第一个符号或左侧的每个符号都可以为空,则它是生产中的前导符号。 (这些集合将包含终端和非终端;现在不必担心。)

  3. 执行以下操作,直到循环期间未更改任何FIRST设置:

    • 对于每个非终端N ,对于FIRSTN )中的每个非终端M ,请将FIRSTM )中的每个元素添加到FIRSTN )中(当然,除非它已经存在)。
  4. 从所有第一组中移除所有非端子。

上面假设您有一个用于计算可空性的算法。 您也可以在《龙书》中找到该算法。 这有点相似。 另外,您应该消除无用的产品; 检测它们的算法与可空性算法非常相似。

有一种算法通常更快,但实际上并不复杂。 完成上述算法的第1步后,您就计算了Leads-withNV )关系,当且仅当非端N的某些生产以端V或非端V开头时,这才成立跳过可为空的非终结符。 FIRST( N )然后是引线的传递性闭包-其范围仅限于终端。 可以使用Floyd-Warshall算法或使用Tarjan算法的一种变体(用于计算图的强连通分量)有效地计算(无递归)。 (例如,请参见Esko Nuutila的可传递性关闭页面。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM