c++ - 将文件读入字符串缓冲区并检测 EOF

标签 c++ file eof fread ftell

我正在打开一个文件并将其内容放入一个字符串缓冲区中,以便对每个字符进行一些词法分析。这样做比使用后续数量的 fread() 调用能够更快地完成解析,并且由于源文件将始终不超过几 MB,我可以放心全部内容文件的一部分将始终被读取。

但是,在检测何时没有更多数据需要解析时似乎有些麻烦,因为ftell() 经常给我一个整数值,该整数值高于文件中的实际字符数.如果尾随字符始终为 -1,则使用 E​​OF (-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 -- 这对于正确的词法分析是不可取的,因为您通常最终会跳过最后的流中的标记在末尾省略了换行符。


在 Basic Latin 字符集中,假设 EOF char 是任何具有负值的字符是否安全?或者也许有更好的方法来解决这个问题?


#EDIT: 我刚刚尝试将 feof() 函数实现到我的循环中,但还是一样,它没有似乎也没有检测到 EOF。

最佳答案

将评论组合成答案...

  • 当您无法读取时,您会泄漏内存(可能是大量内存)。

  • 您不允许在读取的字符串末尾使用空终止符。

  • 当内存即将被文件中的数据覆盖时,将内存清零毫无意义。

  • 您的测试循环正在越界访问内存; nPos == fileSize 超出了您分配的内存的末尾。

    char c = 0;
    LONG nPos = 0;
    while(c != EOF && nPos <= fileSize)
    {
        c = s[nPos];
        // do something with 'c' here...
        nPos++;
    }
    
  • 还有其他问题,之前没有提到。您确实问过是否“假设 EOF 字符是任何具有负值的字符是安全的”,对此我的回答是。这里有几个问题会影响 C 和 C++ 代码。第一个是普通 char 可能是有符号类型或无符号类型。如果类型是无符号的,那么你永远不能在其中存储负值(或者更准确地说,如果你试图将负整数存储到无符号字符中,它将被截断为最低有效位 8* 位,将被视为阳性。

  • 在上面的循环中,可能会出现两个问题之一。如果 char 是有符号类型,则有一个字符(ÿ、y-umlaut、U+00FF、带分音符的拉丁文小写字母 Y、Latin-1 代码集中的 0xFF)具有相同的类型值为 EOF(始终为负,通常为 -1)。因此,您可能会过早地检测到 EOF。如果 char 是无符号类型,则永远不会有任何字符等于 EOF。但是对字符串的 EOF 测试存在根本性的缺陷; EOF 是 I/O 操作的状态指示符,而不是字符。

  • 在 I/O 操作期间,只有在尝试读取不存在的数据时才会检测到 EOF。 fread() 不会报告 EOF;您要求阅读文件中的内容。如果您在 fread() 之后尝试了 getc(fp),您将得到 EOF,除非文件在您测量它的长度后增长了。由于 _wfopen_s() 是一个非标准函数,它可能会影响 ftell() 的行为方式及其报告的值。 (但你后来证实情况并非如此。)

  • 请注意,fgetc()getchar() 等函数被定义为以正整数形式返回字符,以不同的负值形式返回 EOF。

    If the end-of-file indicator for the input stream pointed to by stream is not set and a next character is present, the fgetc function obtains that character as an unsigned char converted to an int.

    If the end-of-file indicator for the stream is set, or if the stream is at end-of-file, the end-of- file indicator for the stream is set and the fgetc function returns EOF. Otherwise, the fgetc function returns the next character from the input stream pointed to by stream. If a read error occurs, the error indicator for the stream is set and the fgetc function returns EOF.289)

    289) An end-of-file and a read error can be distinguished by use of the feof and ferror functions.

    这表明 EOF 如何与 I/O 操作上下文中的任何有效字符分开。

你的评论:

As for any potential memory leakage... At this stage in my project, memory leaks are one of many problems with my code which, as of yet, are of no concern to me. Even if it didn't leak memory, it doesn't even work to begin with, so what's the point? Functionality comes first.

在初始编码阶段阻止错误路径中的内存泄漏比稍后返回并修复它们更容易——因为您可能不会发现它们,因为您可能不会触发错误条件。但是,重要程度取决于该计划的目标受众。如果这是一次性的编码类(class),你可能没问题。如果你是唯一会使用它的人,你可能会没事。但是,如果它将被数以百万计地安装,您将无法在所有地方 retrofit 支票。

I have swapped _wfopen_s() with fopen() and the result from ftell() is the same. However, after changing the corresponding lines to LPSTR s = new char[fileSize + 1], RtlZeroMemory(s, sizeof(char) * fileSize + 1); (which should also null-terminate it, btw), and adding if(nPos == fileSize) to the top of the loop, it now comes out cleanly.

好的。您也可以只使用 s[fileSize] = '\0'; 来终止数据,但是使用 RtlZeroMemory() 可以达到相同的效果(但如果该文件的大小为数兆字节)。但我很高兴各种意见和建议帮助您回到正轨。


* 理论上,CHAR_BITS 可能大于 8;实际上,它几乎总是 8 位,为简单起见,我假设这里是 8 位。如果 CHAR_BITS 为 9 或更大,则讨论必须更加细致,但净效果大致相同。

关于c++ - 将文件读入字符串缓冲区并检测 EOF,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15330202/

相关文章:

bash - 如何让 tclsh 忽略 EOF?

c++ - 在 constexpr 构造函数中复制数组

java -\xef\xbb\xbf 来自 utf8 文件 java

未创建 Oracle 10g 跟踪文件

c - 使用与文件 B 共享的元素(外键)访问文件 A 中的数据

image - Docker - 从 docker repo 拉取失败 (EOF/403) 但从 RH repo 下载有效

c++ - 从 C++ 更改 QML Listview 委托(delegate)

c++ - 在 C++ 中定义虚继承的两种不同方式

java - 在基类构造函数中调用虚方法

eof - fgetc() 不会在 EOF 中停止