繁体   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