使用fread()函数以相反的顺序读取文件会导致内存泄漏吗?

4
我有一个程序,基本上是这样做的:
  1. 打开一些二进制文件
  2. 倒序读取文件(倒序指从EOF开始,以文件开头结束,即从右到左读取文件),使用4MB的块
  3. 关闭文件
我的问题是:为什么内存消耗看起来像下面这样,即使在我的附加代码中没有明显的内存泄漏?
请看上面图片了解程序执行期间的内存消耗情况。
以下是运行以上程序的源代码:
#include <stdio.h>
#include <string.h>

int main(void)
{
    //allocate stuff
    const int bufferSize = 4*1024*1024;
    FILE *fileHandle = fopen("./input.txt", "rb");
    if (!fileHandle)
    {
        fprintf(stderr, "No file for you\n");
        return 1;
    }
    unsigned char *buffer = new unsigned char[bufferSize];
    if (!buffer)
    {
        fprintf(stderr, "No buffer for you\n");
        return 1;
    }

    //get file size. file can be BIG, hence the fseeko() and ftello()
    //instead of fseek() and ftell().
    fseeko(fileHandle, 0, SEEK_END);
    off_t totalSize = ftello(fileHandle);
    fseeko(fileHandle, 0, SEEK_SET);

    //read the file... in reverse order. This is important.
    for (off_t pos = totalSize - bufferSize, j = 0;
        pos >= 0;
        pos -= bufferSize, j ++)
    {
        if (j % 10 == 0)
        {
            fprintf(stderr,
                "reading like crazy: %lld / %lld\n",
                pos, totalSize);
        }

        /*
         * below is the heart of the problem. see notes below
         */
        //seek to desired position
        fseeko(fileHandle, pos, SEEK_SET);
        //read the chunk
        fread(buffer, sizeof(unsigned char), bufferSize, fileHandle);
    }

    fclose(fileHandle);
    delete []buffer;
}

我还有以下观察结果:
  1. 尽管RAM使用量增加了1GB,但整个程序在整个执行过程中只使用了5MB。
  2. 注释调用fread()消除内存泄漏。这很奇怪,因为我没有分配与之相近的任何东西,可能会触发内存泄漏...
  3. 而且,正常读取文件而不是倒序读取(=注释掉fseeko()的调用),也会消除内存泄漏。这是极其奇怪的部分

更多信息...

  1. 以下操作无效:
    1. 检查fread()的结果-没有异常情况。
    2. 切换到普通的32位fseekftell
    3. 执行setbuf(fileHandle, NULL)等操作。
    4. 执行setvbuf(fileHandle, NULL, _IONBF, *任何整数*)等操作。
  2. 使用cygwin和mingw在Windows 7上通过g++ 4.5.3编译,没有进行任何优化,只是g++ test.cpp -o test。两者都表现出这种行为。
  3. 测试中使用的文件长度为4GB,全是零。
  4. 图表中间的奇怪暂停可能是由于某种与此问题无关的临时I/O挂起引起的。
  5. 最后,如果我将读取包装在无限循环中...内存使用量在第一次迭代后停止增加。

我认为这与某种内部缓存有关,直到它被整个文件填满。它在幕后真正工作的方式是什么?如何以可移植的方式防止它?


1
你尝试过用Valgrind(或其他内存调试器)运行它吗? - Jocke
首先,这个 const int bufferSize = 4*1024*1024 不好... 应该是 const int bufferSize = sizeof( int )*1024*1024 - Jacob Pollack
1
@JacobPollack:410241024部分只是4MB。它可以是任何东西,例如8KB。它与整数的大小无关... - rr-
3
我看不到内存泄漏,但在程序运行期间我看到内存使用量非常高,但这与内存泄漏完全不同。 - Tom Tanner
我同意@TomTanner的观点。没有泄漏,你的第五点证明了这一点。 - Carey Gregory
显示剩余2条评论
2个回答

3
我认为这更多是一个操作系统问题(甚至是操作系统资源使用报告问题),而不是您的程序问题。当然,它只使用了5 MB的内存:1 MB用于自身(库、堆栈等),4 MB用于缓冲区。每当您执行fread()时,操作系统似乎会将文件的一部分“绑定”到您的进程,并且释放速度不同。由于您的计算机上的内存使用较低,因此这不是问题:操作系统只是将已读取的数据“挂起”时间比必要的时间长,可能会假设您的应用程序可能会再次读取它,那么它就不必再进行绑定。
如果内存压力更高,则操作系统很可能会更快地取消绑定内存,以便跳跃在您的内存使用历史记录上的情况会更小。

在我看来,倒序读取文件是一项简单的任务。为什么操作系统(Windows 7)要迭代缓存整个4GB的文件,直到内存用尽并不得不使用页面文件呢?(这取决于我的缓冲区大小...) - rr-
磁盘活动本身不会污染 RAM,对吧?在 Win8 上确认了相同的行为。 - rr-
1
谁说“内存会被污染”?仅仅因为操作系统锁定了一个页面(从而将其报告为“已使用”),并不意味着它是脏的。 - Kai Petzke
@rr- 既然你的缓冲区大小是固定的,为什么要动态分配它呢?只需将buffer声明为静态数组即可。 - Carey Gregory
那么这个问题的解决方案是什么?为什么会发生这种情况在讨论中已经很清楚了。是否有办法释放由fread()占用的内存? - kid.abr
显示剩余2条评论

2

我曾经遇到过同样的问题,尽管是在Java中,但在这种情况下并没有关系。我通过一次读取更大的数据块来解决了它。我之前也是读取4Mb大小的块,但当我将其增加到100-200 Mb后,问题就消失了。也许这对你也有用。我使用的是Windows 7。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接