簡體   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