7

を使用して文字列語彙素をコピーするフレックス コードがありますstrdup()

%{   
#include "json.tab.h"
#define YY_DECL extern "C" int yylex()

%}
%option noyywrap

%%

[ \t\n]+ ; 
\"[a-zA-Z]+\" {yylval.sval = strdup(yytext); return STRING; }
[0-9]+ {yylval.ival = atoi(yytext); return NUMBER; }
. {return yytext[0];} ; 

%%

strdup()メモリを割り当て、入力文字列をそれにコピーして返す ( strdup() - C では何をしますか? ) ため、不要になったら解放する必要があると思います。

この投稿から: BISON で %destructor が呼び出されるのはいつですか? %destructor { free($$); printf("free");} STRING、 yacc ファイルに追加しました。

ただし、から返された新しい文字列が に割り当てられfree()ている場合でも、 が呼び出されることはありません。yylval.svalstrdup()

何が間違っている可能性がありますか? フレックス/バイソンで割り当てられた文字列を解放する方法は?

追加した

次のように静的に割り当てられた sval を使用することを考えます。

%union {
    int ival;
    char sval[100]; // char* sval;
}

フレックスコードは次のようになります (yytext が 100 バイト未満の場合はチェックコードなし):

\"[a-zA-Z]+\" {
    //yylval.sval = strdup(yytext);
    memset(yylval.sval, 0, 100);
    strcpy(yylval.sval, yytext);
    return STRING; 
}

このアプローチが人々が通常使用するものであるかどうかはわかりません。

追加2

私のアプリケーションでは、簡単なインターンシップで問題ありません。

extern char buffer[]; // [100];
%}
%option noyywrap

%%

\"[a-zA-Z]+\" {
        //yylval.sval = strdup(yytext);
        memset(buffer, 0, 100);
        strcpy(buffer, yytext);
        yylval.sval = buffer;
        return STRING; 
    }
...

char buffer[100];

yacc コードの場合

%union {
    int ival;
    char *sval; 
}
4

1 に答える 1

9

あなたが言うように、「もう必要なくなったら」文字列を解放する必要があります。それはそれと同じくらい単純(または複雑)です。

C にはガベージ コレクタがないため、C プログラマは、割り当てられたメモリがいつ不要になるかを知る責任があります。言語はそれを理解しようとはしませんし、(ほとんどの場合) バイソンもそうしません。

割り当てられたメモリへのポインターを含む 1 つ以上のセマンティック値が指定されたリダクション ルールがある場合、そのルールは多くのことのいずれかを行う可能性があります。通常、ポインターのみをコピーすることによって、セマンティック値を新しいセマンティック値に渡す場合があります。セマンティック値をコピーしてから、元の値を解放する場合があります。シンボル テーブルのような解析グローバル データ構造にセマンティック値を追加する場合があります。

これらすべての場合において、プログラマーは、割り当てられたメモリがまだ必要かどうかを認識している必要があり、必要でない場合は割り当てを解放する必要があります。

ただし、bison がリダクション アクションに提示されることなくセマンティック値を破棄する場合がいくつかあります。これらのほとんどはエラー状態です。エラー回復の一環として、bison がトークンを破棄することを決定した場合、そのトークンのセマンティック値はメモリ リークを起こす可能性があります。%destructorそして、バイソンが宣言を持っているのはまさにこの場合です。この%destructorコードは、エラー回復またはエラー後のクリーンアップの結果として bison がトークンを破棄した場合 (およびその場合にのみ) 呼び出されます。その他の場合はすべて自己責任です。

スタック スロットを膨大にすることでこの責任を回避しようとする (char[100]セマンティック値の結合に a を含めるなど) ことは、安全ではなく非効率的です。固定空間バッファーがオーバーフローする可能性があることを常に認識しておく必要があるため、安全ではありません。つまり、構文的に有効なプログラムを解析すると、任意のメモリが上書きされる可能性があります。スタックを必要以上に数桁大きくすることになるため、非効率的です。また、スタックスロットを常にコピーすることになるためです(デフォルトアクションを使用するものであっても、すべてのリダクションルールに対して少なくとも2回。)

セマンティック値の有効期間を把握することは、メモリを共有する場合にのみ複雑になります。これは通常、(例のように) 文字列リテラルには役立ちませんが、変数名には非常に役立ちます。ほとんどの名前はプログラム内で複数回出現するため、出現ごとに同じ文字列を使用する誘惑が常にあります。

通常、識別子の問題は、文字列をレクサーに「インターン」することで解決します。lexer は、解析グローバル名テーブル (たとえば、setハッシュ テーブルで実装された単純なもの) を維持し、検出した識別子ごとに、名前テーブルに識別子を追加し、一意の名前エントリ ポインターをセマンティック値として渡します。解析終了後のある時点で、名前テーブル全体を解放して、すべての識別子を解放できます。

文字列リテラルやその他のおそらく一意の文字列については、とにかく名前テーブルを使用するか、同じ文字列へのポインターの 2 つのコピーを避けることができます。名前テーブルを使用すると、メモリ管理で必要な作業量を削減できるという利点がありますが、余分な時間のために不要な文字列を保持する可能性があります。それは解析結果の性質に大きく依存します: AST の場合、おそらく AST が存在する限り文字列を保持する必要がありますが、直接実行またはワンパス コード生成を行っている場合は、長期的には文字列リテラルは必要ありません。

于 2015-06-28T22:28:51.413 に答える