[英]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)通过使用
feof
和ferror
函数可以区分文件结束和读取错误。
这表明在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.