【问题标题】:Reading a file into a string buffer and detecting EOF将文件读入字符串缓冲区并检测 EOF
【发布时间】:2013-03-11 02:14:33
【问题描述】:

我正在打开一个文件并将其内容放入一个字符串缓冲区中,以便对每个字符进行一些词法分析。这样做可以比使用后续的 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 更高的值——这对于正确的词法分析来说是不可取的,因为您通常最终会跳过最后的流中的标记,在末尾省略换行符。


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


#EDIT: 我刚刚尝试将 feof() 函数实现到我的循环中,但仍然没有似乎也无法检测到 EOF。

【问题讨论】:

  • 阅读失败时(可能)会泄漏大量内存。您不允许在读取的字符串末尾使用空终止符。当内存即将被文件中的数据覆盖时,将内存归零是没有意义的。您的测试循环正在越界访问内存; nPos == fileSize 超出了您分配的内存的末尾。
  • 您是否有理由标记此 C++,但使用在我看来更像纯 C 的东西?你试过 C++ 文件流吗?你的文件是什么编码的?
  • 只有在尝试读取不存在的数据时才会检测到 EOF。 fread() 不会报告 EOF;您要求阅读文件中的内容。如果你在fread() 之后尝试getc(fp),你会得到EOF,除非文件在你测量它的长度后已经增长。由于_wfopen_s() 是一个非标准函数,它可能会影响ftell() 的行为方式及其报告的值。 没有;假设任何负 char 值都是 EOF 是不安全的。普通类型 char 可以是有符号或无符号的。
  • @WarrenP:由于new[fileSize],它被正确标记为 C++。它可能不是惯用的 C++,但绝对不是 C。
  • EOF 未在缓冲区中编码。它是从fgetc() or fgetwc() 返回的,具体取决于您处理文件本身的方式,并且与您的方式无关。但是您以二进制模式打开文件,老实说,我什至不知道它是 supportedccs 编码模式。如果您使用计算文件长度+1(终止符的+1),则缓冲区的大小应以字节为单位适当。如果以二进制模式打开并指定编码提示以请求 BOM 分析,那就更好了。

标签: c++ file eof fread ftell


【解决方案1】:

将 cmets 组装成答案...

  • 读取失败时会泄漏内存(可能是大量内存)。

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

  • 当内存即将被文件中的数据覆盖时,将内存归零是没有意义的。

  • 您的测试循环正在越界访问内存; 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* sup> 位,将被视为正数。

  • 在上面的循环中,可能会出现两个问题之一。如果char 是有符号类型,则存在与 EOF 具有相同值的字符(ÿ、y-变音符号、U+00FF、LATIN SMALL LETTER Y WITH DIAERESIS、0xFF 在 Latin-1 代码集中)总是负数,通常是-1)。因此,您可能会过早地检测到 EOF。如果char 是无符号类型,则永远不会有任何字符等于 EOF。但是对字符串 EOF 的测试存在根本缺陷; EOF 是来自 I/O 操作的状态指示器,而不是字符。

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

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

    如果stream 指向的输入流的文件结束指示符未设置并且 如果存在下一个字符,fgetc 函数会将该字符作为 unsigned char 转换为 int

    如果设置了流的文件结束指示符,或者如果流处于文件结束,则结束- 流的文件指示符已设置,fgetc 函数返回 EOF。否则,该 fgetc 函数从stream 指向的输入流中返回下一个字符。 如果发生读取错误,则设置流的错误指示符并使用 fgetc 函数 返回 EOF。289)

    289) 使用 feofferror 函数可以区分文件结束和读取错误。

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

你评论:

至于任何潜在的内存泄漏......在我的项目的这个阶段,内存泄漏是我的代码的众多问题之一,到目前为止,我并不关心这些问题。即使它没有泄漏内存,它甚至一开始都不起作用,那有什么意义呢?功能至上。

在初始编码阶段阻止错误路径中的内存泄漏比稍后返回并修复它们更容易 - 因为您可能不会发现它们,因为您可能不会触发错误条件。但是,重要程度取决于该计划的目标受众。如果它是一次性的编码课程,你可能没问题。如果你是唯一会使用它的人,你可能会没事。但是,如果它会被数百万安装,那么您将在各处改装检查时遇到问题。

我已将 _wfopen_s() 与 fopen() 交换,并且 ftell() 的结果是相同的。但是,将相应的行更改为 LPSTR s = new char[fileSize + 1], RtlZeroMemory(s, sizeof(char) * fileSize + 1); (顺便说一句,这也应该终止它),并将 if(nPos == fileSize) 添加到循环的顶部,它现在干净利落地出来了。

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


* 理论上,CHAR_BITS 可能大于 8;在实践中它几乎总是 8 位,为了简单起见,我假设它是 8 位。如果 CHAR_BITS 为 9 或更大,则讨论必须更加细致入微,但最终效果大致相同。

【讨论】:

    猜你喜欢
    • 2014-01-06
    • 2018-10-02
    • 1970-01-01
    • 2013-01-16
    • 2012-01-28
    • 1970-01-01
    • 2012-11-17
    • 2018-07-21
    • 2019-11-26
    相关资源
    最近更新 更多