我打开一个文件并将其内容放入字符串缓冲区,以便在每个字符的基础上进行一些词法分析。这样做可以使解析比使用后续数量的 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。
答案 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)使用
feof
和ferror
函数可以区分文件结束和读取错误。
这表示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或更高,讨论必须更加细致,但净效果大致相同。