0

我正在打开一个文件并将其内容放入字符串缓冲区中,以对每个字符进行一些词法分析。这样做可以比使用后续的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。

4

1 回答 1

1

将评论组装成答案...

  • 当您阅读失败时,您会泄漏内存(可能是大量内存)。

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

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

  • 您的测试循环正在越界访问内存;nPos == fileSize超出您分配的内存的末尾。

    char c = 0;
    LONG nPos = 0;
    while(c != EOF && nPos <= fileSize)
    {
        c = s[nPos];
        // do something with 'c' here...
        nPos++;
    }
    
  • 还有其他问题,之前没有提到过。您确实问过“假设 EOF char 是任何具有负值的字符是否安全”,我对此做出了回答No。这里有几个问题会影响 C 和 C++ 代码。首先是 plainchar可能是有符号类型或无符号类型。如果类型是无符号的,那么您永远不能在其中存储负值(或者,更准确地说,如果您尝试将负整数存储到无符号字符中,它将被截断为最低有效 8 *位并将被处理为阳性。

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

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

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

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

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

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

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

你评论:

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

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

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

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


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

于 2013-03-11T03:59:47.840 回答