簡體   English   中英

將文件讀入字符串緩沖區並檢測EOF

[英]Reading a file into a string buffer and detecting EOF

我正在打開一個文件,並將其內容放入字符串緩沖區中,以便根據每個字符進行一些詞法分析。 通過這種方式,解析可以比使用隨后的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;
}

這似乎總是可以正常工作。 這之后是一個簡單的循環,它一次檢查一個字符的字符串緩沖區的內容,如下所示:

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為止 -這對於正確的詞法分析是不理想的,因為您通常最終會跳過流中的最后一個標記,該標記最后會忽略換行符。


在基本拉丁字符集中,可以安全地假設EOF字符是任何具有負值的字符嗎? 也許只有更好的方法可以解決此問題?


#EDIT:我剛剛嘗試將feof()函數實現到我的循環中,並且都一樣,它似乎也無法檢測到EOF。

將評論匯總為答案...

  • 當您無法讀取時,您會泄漏內存(可能會占用大量內存)。

  • 您不允許在讀取的字符串末尾使用空終止符。

  • 當文件中的數據全部將其覆蓋時,將內存清零沒有任何意義。

  • 您的測試循環正在訪問內存。 nPos == fileSize是超出分配的內存末尾的1。

     char c = 0; LONG nPos = 0; while(c != EOF && nPos <= fileSize) { c = s[nPos]; // do something with 'c' here... nPos++; } 
  • 這樣做還有其他問題,以前沒有提到。 您確實問過“是否可以安全地假定EOF字符是具有負值的任何字符”,對此我回答了 這里有幾個影響C和C ++代碼的問題。 第一個是普通char可以是有符號類型或無符號類型。 如果類型是無符號的,則您永遠不能在其中存儲負值(或更准確地說,如果您嘗試將負整數存儲到無符號的char中,它將被截斷為最低有效8 *位,並將被處理為積極。

  • 在上面的循環中,可能會出現兩個問題之一。 如果char是帶符號的類型,則存在一個字符(ÿ,y-umlaut,U + 00FF,帶DIAERESIS的拉丁小寫字母Y,Latin-1代碼集中的0xFF),其值與EOF相同(始終為負數,通常為-1)。 因此,您可能會過早檢測到EOF。 如果char是無符號類型,則永遠不會有等於EOF的字符。 但是對字符串進行EOF的測試從根本上來說是有缺陷的。 EOF是來自I / O操作的狀態指示器,而不是字符。

  • 在I / O操作期間,只有在嘗試讀取不存在的數據時,您才會檢測到EOF。 fread()不會報告EOF; 您要求讀取文件中的內容。 如果您在fread() getc(fp)之后嘗試了getc(fp) ,那么除非文件由於測量了文件的長度而變大,否則將獲得EOF。 由於_wfopen_s()是非標准函數,因此可能會影響ftell()行為方式和報告的值。 (但是您后來發現事實並非如此。)

  • 請注意,諸如fgetc()getchar()類的函數已定義為以正整數形式返回字符,而以不同的負值形式返回EOF。

    如果輸入流中的結束文件指針指向的stream沒有設置和下一個字符存在,則fgetc函數獲取字符作為unsigned char轉換為int

    如果設置了流的文件結束指示符,或者流在文件末尾,則設置了流的文件結束指示符,並且fgetc函數返回EOF。 否則, fgetc函數返回從輸入流中的下一個字符被指向stream 如果發生讀取錯誤,將設置流的錯誤指示符,並且fgetc函數將返回EOF。 289)

    289)通過使用feofferror函數可以區分文件結束和讀取錯誤。

    這表明在I / O操作的上下文中EOF如何與任何有效字符分開。

您評論:

至於任何潛在的內存泄漏...在我的項目的現階段,內存泄漏是我的代碼存在的許多問題之一,到目前為止,我仍然不關心它們。 即使它沒有泄漏內存,也從一開始就不起作用,那又有什么意義呢? 功能至上。

在最初的編碼階段,避免在錯誤路徑中出現內存泄漏比以后再修復它們更容易-因為您可能沒有發現它們,因為您可能不會觸發錯誤情況。 但是,重要的程度取決於計划的目標受眾。 如果這是一次性的編碼課程,則可能會很好。 如果您是唯一使用它的人,則可能會很好。 但是,如果將要安裝數以百萬計的設備,則到處都會加裝檢查設備。

我已經將_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()可以達到相同的效果(但是如果文件大小為數MB,則速度會較慢)。 但我很高興收到各種評論和建議,使您重回正軌。


*理論上,CHAR_BITS可能大於8; 實際上,它幾乎總是8,為簡單起見,我假設這里是8位。 如果CHAR_BITS為9或更大,則討論必須更加細微,但最終效果幾乎相同。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM