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

时间:2013-03-11 02:14:33

标签: c++ file eof fread ftell

我打开一个文件并将其内容放入字符串缓冲区,以便在每个字符的基础上进行一些词法分析。这样做可以使解析比使用后续数量的 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)< / em>字符,因此永远不会检测到EOF。相反,循环只是继续向前,直到 nPos 最终具有比 fileSize 更高的值 - 这对于正确的词法分析是不可取的,因为你经常最终跳过最后的流中的标记,在末尾省略换行符。


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


#EDIT: 我刚尝试将 feof()函数实现到我的循环中,所有相同的,它没有'似乎也检测到了EOF。

1 个答案:

答案 0 :(得分:1)

将评论汇总到答案......

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

  • 您在字符串读取结束时不允许使用空终止符。

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

  • 您的测试循环正在访问内存越界; 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可以是签名类型或无符号类型。如果类型是无符号的,那么你永远不能在其中存储负值(或者更准确地说,如果你试图将负整数存储到unsigned char中,它将被截断为最不重要的8 * 位,将被视为正面。

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

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

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

      

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

         

    如果设置了流的文件结束指示符,或者流是在文件末尾,则结束 -   设置了流的文件指示符,fgetc函数返回EOF。否则,   fgetc函数返回stream指向的输入流中的下一个字符。   如果发生读取错误,则设置流的错误指示符和fgetc功能   返回EOF。 289)

         

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

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

你发表评论:

  

至于任何潜在的内存泄漏......在我的项目的这个阶段,内存泄漏是我的代码的许多问题之一,到目前为止,我并不关心。即使它没有泄漏记忆,它甚至不起作用,那么重点是什么?功能第一。

在初始编码阶段更容易阻止错误路径中的内存泄漏,而不是稍后返回并修复它们 - 因为您可能无法发现它们,因为您可能无法触发错误情况。但是,重要程度取决于该计划的目标受众。如果它是编码课程的一次性,你可能没事。如果你是唯一一个会使用它的人,你可能没事。但是,如果它将由数百万人安装,那么在任何地方都会有问题进行改造。

  

我已经用fopen()交换了_wfopen_s(),而ftell()的结果是相同的。但是,将相应的行更改为LPSTR后s = new char [fileSize + 1],RtlZeroMemory(s,sizeof(char)* fileSize + 1); (也应该将它终止,btw),并将if(nPos == fileSize)添加到循环的顶部,它现在干净利落。

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


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