c++ - fgets() 似乎没有正确移动文件指针

标签 c++ fgets fseek ftell

我有一个应用程序从一个文本文件中读取,而该文本文件正在被另一个应用程序同时写入。读取文件的应用程序使用 C - fopen 以文本模式打开它。文件中的行可能很大,大小可达几百兆。出于这个原因,我们有一个函数可以使用 fgets 从文件中读取 4K block 并将其附加到字符串对象中,直到它设法读取完整的一行。尝试读取时,写入文件的应用程序可能写入了部分行。 Out 自定义 ReadLine 函数通过检测文件结尾、将文件指针重新设置到最后一个已知的正确位置并丢弃已经读取的文本来处理这种情况。

下面是该函数的样子:

#define MAX_BUF_SIZE 4096

bool ReadLine(FILE* fp, std::string& result, bool& isEof) {

    result.clear();
    long const lastOffset = ftell(fp);
    bool hasReadOneLine = false;
    isEof = false;

    debug_print(lastOffset);

    while (!hasReadOneLine && !isEof) {

        char dataRead[MAX_BUF_SIZE];
        memset(dataRead, 0, sizeof(dataRead));

        if (fgets(dataRead, MAX_BUF_SIZE, fp) == NULL) {

            if (feof(fp)) {
                debug_print("Flag 1");
                isEof = true;
            } else {
                debug_print("Flag 2");
                result.clear();
                fseek(fp, lastOffset, SEEK_SET); //reset the file pointer to where it was
                return false;
            }

        }

        result += dataRead;
        hasReadOneLine = (result[result.length()-1] == '\n');

    } // end loop

    if (!hasReadOneLine) {
        debug_print("Flag 3");
        result.clear();
        fseek(fp, lastOffset, SEEK_SET); //reset the file pointer to where it was
        return false;
    }

    // drop the new-line character ...
    if (result[ result.length()-1] == '\n') {
        result.resize(result.size() - 1);
    }

    return true;

}

问题: 我遇到过这样一种情况,在从文件中读取整行后,ReadLine() 函数在再次调用以读取下一行时返回先前读取的行的最后一个 block 。我记录了 ftell() 返回的 lastOffset 的值,并注意到在这种罕见的情况下,fgets 没有将文件指针移动到它读取的行的末尾。

我添加了一些调试行,但在我的例子中,唯一打印的是 lastOffset 值。

在 ReadLine 返回不完整行的调用中,lastOffset 的值为:21563617 不完整行的长度为:920

在返回完整行之前的调用中,lastOffset 的值为:21442207 上一次调用中读取的行的长度是:122331(包括换行符)

我的问题是:有没有人遇到过类似的问题?您对可能出现的问题有何看法?我不一定要寻找完整的答案,而只是寻找一些可能出错的提示。

**更新**

我设法用一个小实用程序重现了这个问题,以 4K 的 block 写入一个文件, sleep 间隔为 10 毫秒,而另一个程序(使用上述功能)同时从同一个文件读取。

看起来像执行 fseek() 来重新设置文件指针在上面的函数中是一个错误的选择,因为将文件指针重新设置到以前的位置并不一定会清除 C 库自己的内部缓冲区。我仍然不完全相信这个解释,因为在某些情况下(重现案例)文件指针重置从未发生过。

无论如何,我在网上做了更多搜索,有些线程似乎建议使用较低级别的流并在库本身中处理缓冲。所以我改变了上面函数的实现和它的其他助手来做到这一点。我现在使用 Windows 的 _sopen_s()/_read()/_lseek() 和 Linux/Solaris 的标准 POSIX 接口(interface)来执行较低级别的 IO 处理。通过这些更改,它似乎起作用了,我再也看不到这个问题了。

谢谢大家的宝贵时间。非常感谢您的所有意见。

苏曼

** 更新 2 **

现在我肯定知道原因了。问题是如果文件以文本模式打开,ftell() 和 fseek() 是不可靠的。如果文件以二进制模式打开,则上面的函数可以正常工作。

这是指向其他人之前发现此问题的文章的链接:http://arstechnica.com/civis/viewtopic.php?f=20&t=420490

这是一件好事,因为现在我有了一个需要更改 1 行而不是 200 行的修复程序! :-)

最佳答案

如果您的最大行大小小于 MAX_BUF_SIZE,那么您可能需要考虑一个可以大大简化您的实现的替代解决方案。简而言之,使用 fread 而不是 fgets:

void ReadLine(FILE* fp, std::string& result, bool& isEof)
{
    static char dataRead[MAX_BUF_SIZE] = {0};
    static int  dataindex = 0;
    int datalength = fread(dataRead,MAX_BUF_SIZE-dataindex,1,fp);
    for (int i=0; dataRead[i]!='\n'; i++)
        result += dataRead[i];
    dataindex = result.length()+1;
    memmove(dataRead,dataRead+dataindex,datalength-dataindex);
    isEof = feof(fp);
}

注意事项:

  1. 此实现假定最后一行(因此文件本身)以换行符结尾。

  2. 您可以使用dataRead/dataindex作为循环缓冲区以避免memmove操作。

关于c++ - fgets() 似乎没有正确移动文件指针,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21388323/

相关文章:

c++ - 使用 libCurl 进行 POST 添加未知的页眉和页脚

c - 如何删除从 C 中的 fgets 输入的额外字符?

C 读取文件后无法写入

c++ - Quest 库(Quest 身份验证服务)线程安全吗?

c++ - 带有 int 和 UINT32 的警告 C4018,但不带有 int 和 UCHAR

c++ - 链表的几种实现——C++

c - 第二次 fgets 后出现段错误

c - 为什么我的 fputc/fwrite 一直以十六进制打印以及如何打印到文件的中间?

c - fseek() 和 ftell() 在循环中失败

ffmpeg - 在 FFmpeg 中按字节查找