0

ファイルを開き、その内容を文字列バッファーに配置して、文字ごとに字句解析を行っています。このようにすると、後続のfread()呼び出しを使用するよりも速く解析を終了できます。また、ソース ファイルは常に数 MB を超えないため、ファイルの内容全体が常に読み取られるので安心できます。 .

ただし、ftell()はファイル内の実際の文字数よりも大きな整数値を返すことが多いため、解析するデータがなくなったことを検出する際に問題があるようです。これは、末尾の文字が常に -1 である場合、EOF (-1) マクロを使用しても問題にはなりません...しかし、常にそうであるとは限りません...


ファイルを開き、文字列バッファに読み込む方法は次のとおりです。

FILE *fp = NULL;
errno_t err = _wfopen_s(&fp, m_sourceFile, L"rb, ccs=UNICODE");
if(fp == NULL || err != 0) return FALSE;
if(fseek(fp, 0, SEEK_END) != 0) {
    fclose(fp);
    fp = NULL;
    return FALSE;
}

LONG fileSize = ftell(fp);
if(fileSize == -1L) {
    fclose(fp);
    fp = NULL;
    return FALSE;
}
rewind(fp);

LPSTR s = new char[fileSize];
RtlZeroMemory(s, sizeof(char) * fileSize);
DWORD dwBytesRead = 0;
if(fread(s, sizeof(char), fileSize, fp) != fileSize) {
    fclose(fp);
    fp = NULL;
    return FALSE;
}

これは常に完全にうまく機能しているように見えます。これに続くのは単純なループで、次のように文字列バッファの内容を一度に 1 文字ずつチェックします。

char c = 0;
LONG nPos = 0;
while(c != EOF && nPos <= fileSize)
{
    c = s[nPos];
    // do something with 'c' here...
    nPos++;
}

ファイルの末尾のバイトは通常、一連のý (-3)および« (-85)文字であるため、EOF は検出されません。代わりに、nPos が fileSize よりも高い値になるまでループが続きますこれは、適切な語彙分析には望ましくありません。これは、末尾の改行文字を省略したストリームで最終トークンをスキップすることがよくあるためです。


Basic Latin 文字セットでは、EOF char が負の値を持つ任意の文字であると想定しても安全でしょうか? それとも、これを行うためのより良い方法がありますか?


#EDIT: feof()関数をループに実装しようとしましたが、EOF も検出されないようです。

4

1 に答える 1

1

コメントを回答にまとめています...

  • 読み取りに失敗すると、メモリ リーク (潜在的に大量のメモリ) が発生します。

  • 読み取った文字列の末尾にヌル ターミネータを許可していません。

  • ファイルからのデータによってすべてが上書きされようとしているときに、メモリをゼロにしても意味がありません。

  • テスト ループが範囲外のメモリにアクセスしています。nPos == fileSizeは、割り当てたメモリの最後を超えています。

    char c = 0;
    LONG nPos = 0;
    while(c != EOF && nPos <= fileSize)
    {
        c = s[nPos];
        // do something with 'c' here...
        nPos++;
    }
    
  • これには、前述されていない他の問題があります。「EOF char は負の値を持つ任意の文字であると想定しても安全か」と尋ねられましたが、私はNoと答えました。ここには、C と C++ コードの両方に影響するいくつかの問題があります。1 つ目は、plaincharは符号付きタイプまたは符号なしタイプの可能性があるということです。型が unsigned の場合、負の値を格納することはできません (より正確には、負の整数を unsigned char に格納しようとすると、最下位の 8 *ビットに切り捨てられ、処理されます)。ポジティブに。

  • 上記のループでは、2 つの問題のいずれかが発生する可能性があります。が符号付きタイプの場合char、EOF (常に負) と同じ値を持つ文字 (ÿ、y ウムラウト、U+00FF、分音符付きラテン小文字 Y、Latin-1 コード セットの 0xFF) があります。通常は -1)。したがって、EOF を時期尚早に検出する可能性があります。が unsigned 型の場合char、EOF に等しい文字は存在しません。しかし、文字列の EOF のテストには根本的な欠陥があります。EOF は、文字ではなく、I/O 操作からのステータス インジケータです。

  • I/O 操作中に、存在しないデータを読み取ろうとしたときにのみ EOF を検出します。はfread()EOF を報告しません。ファイルの内容を読み取るように要求しました。getc(fp)の後に試したfread()場合、ファイルの長さを測定してからファイルが大きくならない限り、EOF が発生します。_wfopen_s()は非標準の関数であるため、 の動作や報告する値に影響を与える可能性がありますftell()。(しかし、後でそうではないことがわかりました。)

  • fgetc()やなどの関数getchar()は、文字を正の整数として返し、EOF を明確な負の値として返すように定義されていることに注意してください。

    が指す入力ストリームのファイル終了標識がstream設定されておらず、次の文字が存在する場合、関数はその文字を に変換されたfgetcとして取得します。unsigned charint

    ストリームのファイル終了インジケータが設定されている場合、またはストリームがファイルの終わりにある場合、ストリームのファイル終了インジケータが設定され、fgetc関数は EOF を返します。それ以外の場合、 fgetc関数は が指す入力ストリームから次の文字を返しますstream。読み取りエラーが発生した場合、ストリームのエラー インジケータが設定され、fgetc関数は EOF を返します。289)

    feof289) ファイルの終わりと読み取りエラーは、関数と関数を使用して区別できますferror

    これは、IOF が I/O 操作のコンテキストで有効な文字からどのように分離されているかを示しています。

あなたのコメント:

メモリ リークの可能性については... 私のプロジェクトのこの段階では、メモリ リークは私のコードの多くの問題の 1 つであり、現時点では私には関係ありません。メモリ リークが発生していなくても、そもそも機能しないのに、何の意味があるのでしょうか。機能性が第一です。

後で戻って修正するよりも、最初のコーディング段階でエラー パスのメモリ リークを防ぐ方が簡単です。ただし、それがどの程度重要かは、プログラムの対象視聴者によって異なります。コーディング コースの 1 回限りの場合は、問題ない可能性があります。あなただけがそれを使うなら、あなたは大丈夫かもしれません。しかし、何百万人もがインストールする場合、チェックをどこにでも後付けするのに問題が発生します。

_wfopen_s() を fopen() と交換しましたが、 ftell() の結果は同じです。ただし、対応する行を LPSTR に変更した後 s = new char[fileSize + 1], RtlZeroMemory(s, sizeof(char) * fileSize + 1); (これもnullで終了する必要があります)、ループの先頭にif(nPos == fileSize)を追加すると、きれいに出力されます。

わかった。s[fileSize] = '\0';を使用してデータを null で終了することもできますが、を使用しRtlZeroMemory()ても同じ効果が得られます (ただし、ファイルのサイズが数メガバイトの場合は遅くなります)。しかし、さまざまなコメントや提案があなたを軌道に乗せるのに役立ったことをうれしく思います.


* 理論的には、CHAR_BITS は 8 より大きい可能性があります。実際には、ほとんどの場合 8 であり、簡単にするために、ここでは 8 ビットであると想定しています。CHAR_BITS が 9 以上の場合、議論はさらに微妙になりますが、最終的な効果はほとんど同じです。

于 2013-03-11T03:59:47.840 に答える