Realloc()无法正确释放Windows中的内存

时间:2012-02-06 18:00:31

标签: windows memory realloc

我正在尝试在Windows应用程序中使用 realloc()。我正在分配一大块内存,然后在我知道正确的大小后使用 realloc()将其缩小。

我发现尽管 realloc()似乎正常工作(任务管理器中的内存反映了您的预期),但应用程序最终会耗尽内存。据我所知,就好像 relloc()释放内存但不释放与内存关联的虚拟地址空间。因此, malloc()最终会失败。

这是一个小型控制台应用程序,用于演示此问题:

int _tmain(int argc, _TCHAR* argv[])
{
    static const DWORD dwAllocSize = (50 * 1024 * 1024);
    static const DWORD dwReallocSize = 10240;
    static const DWORD dwMaxIterations = 200;

    BYTE* arpMemory[dwMaxIterations];
    memset( arpMemory, 0, sizeof(arpMemory) );

    for( DWORD i = 0; i < dwMaxIterations; i++ )
    {
        arpMemory[i] = (BYTE*) malloc( dwAllocSize );
        if( !arpMemory[i] )
        {
            printf("OUT OF MEMORY after %d iterations!\n", i);
            return -1;
        }

        BYTE* pRealloc = (BYTE*) realloc( arpMemory[i], dwReallocSize );
        if( !pRealloc )
        {
            printf("Realloc FAILED after %d iterations!\n", i);
            return -1;
        }
        else if( pRealloc != arpMemory[i] )
        {
            printf("Warning: Pointer changed: 0x%08X -> 0x%08X\n", arpMemory[i], pRealloc);
            arpMemory[i] = pRealloc;
        }
    }

    printf("Success!\n");

    for( int i = 0; i < dwMaxIterations; i++ )
        free( arpMemory[i] );

    return 0;
}

应用程序重复分配50 MB内存,然后立即将其大小调整为仅10K。如果你运行它,你会发现它仅在38次迭代后失败并出现OUT OF MEMORY错误。这相当于2GB最初分配的内存 - 这是Windows应用程序的地址空间限制。

有趣的是,如果你查看任务管理器,你会看到应用程序几乎没有任何内存。然而 malloc()失败了。这就是让我相信虚拟地址空间耗尽的原因。

(另一个尝试的实验是注释掉重新分配,因此没有内存被释放或重新分配。应用程序在完全相同的位置失败:经过38次迭代。唯一的区别是这次任务管理器反映了完整的2GB使用。)

最后一点信息:这个应用程序在Linux下运行。所以这个 realloc()问题严格来说只限于Windows。

有什么想法吗?

2 个答案:

答案 0 :(得分:4)

你正在分割堆这样做。无论你使用realloc()释放什么,都会被添加到空闲块列表中。永远不要再使用,因为你总是要求一个比那个更大的新块。这些空闲块只会累积占用虚拟内存,直到不再剩下。当你一次丢掉几乎50兆字节时,发生得非常快。

你需要重新考虑你的方法。

答案 1 :(得分:3)

经过一些实验,并在文档的各行之间阅读,我得出的结论是,大内存分配(32位略低于512k,64位略低于1MB)使用分配的地址空间VirtualAlloc并为该特定内存块保留。

如果像汉斯建议的那样暂时增加缓冲区大小是不切实际的,而且你不想复制数据,我认为你唯一的另一个选择就是保留一块足够大的地址空间来容纳你所有的缓冲并自己分配空间。这有多复杂取决于诸如应用程序如何使用和释放缓冲区,是否一次写入多个缓冲区以及在应用程序的生命周期内将处理多少数据等因素。

附加:这里有一些测试代码显示我看到的情况:

#include <windows.h>

#include <stdio.h>

DWORD reallocSize = 0x01000;    // 4K

void walkheap(HANDLE heap)
{
    PROCESS_HEAP_ENTRY phe;
    MEMORY_BASIC_INFORMATION mbi;

    phe.lpData = NULL;

    for (;;)
    {
        if (!HeapWalk(heap, &phe))
        {
            printf("HeapWalk: %u\n", GetLastError());
            return;
        }
        printf("%08x %08x %08x %08x %08x ", phe.lpData, phe.cbData, phe.cbOverhead, phe.iRegionIndex, phe.wFlags);
        if (VirtualQuery(phe.lpData, &mbi, sizeof(mbi)) != 0)
        {
            printf("--> %08x\n",mbi.AllocationBase);
        }
        else
        {
            printf("--> (error %u)\n", GetLastError());
        }
    }
}

void alloc(HANDLE heap, DWORD count, DWORD size)
{
    BYTE* ptr;
    BYTE* pRealloc;

    ptr = (BYTE *)HeapAlloc(heap, 0, size);
    printf("Pointer %u is %08x (%08x)\n", count, ptr, size);

    pRealloc = (BYTE*) HeapReAlloc( heap, 0, ptr, reallocSize);
    if( pRealloc != ptr)
    {
        printf("Pointer %u changed to %08x\n", count, pRealloc);
    }
}

int main(int argc, char ** argv)
{
    HANDLE heap;

    heap = HeapCreate(0, 0, 0);
    if (heap == NULL)
    {
        printf("HeapCreate: %u\n", GetLastError());
        return 1;
    }

    walkheap(heap);

    alloc(heap, 1, 0x08000);
    alloc(heap, 2, 0x08000);
    alloc(heap, 3, 0x08000);
    alloc(heap, 4, 0x08000);

    alloc(heap, 10, 0x20000000);
    alloc(heap, 11, 0x20000000);
    alloc(heap, 12, 0x20000000);
    alloc(heap, 13, 0x20000000);

    alloc(heap, 20, 0x10000000);
    alloc(heap, 21, 0x10000000);
    alloc(heap, 22, 0x10000000);
    alloc(heap, 23, 0x10000000);

    walkheap(heap);

    return 0;
}

和我的结果(参见PROCESS_HEAP_ENTRY结构):

Address  Alloc    Overhead Region   Flags        Virtual Address Range Base

00420000 00000588 00000000 00000000 00000001 --> 00420000
004207e8 000007f8 00000010 00000000 00000000 --> 00420000
00421000 0003f000 00000000 00000000 00000002 --> 00420000
HeapWalk: 259
Pointer 1 is 004207e0 (00008000)
Pointer 2 is 004217f8 (00008000)
Pointer 3 is 00422810 (00008000)
Pointer 4 is 00423828 (00008000)
Pointer 10 is 00740020 (20000000)
Pointer 11 is 20750020 (20000000)
Pointer 12 is 52580020 (20000000)
Pointer 13 is 00000000 (20000000)
Pointer 20 is 40760020 (10000000)
Pointer 21 is 00000000 (10000000)
Pointer 22 is 00000000 (10000000)
Pointer 23 is 00000000 (10000000)
00420000 00000588 00000000 00000000 00000001 --> 00420000
004207e0 00001000 00000018 00000000 00000004 --> 00420000
004217f8 00001000 00000018 00000000 00000004 --> 00420000
00422810 00001000 00000018 00000000 00000004 --> 00420000
00423828 00001000 00000018 00000000 00000004 --> 00420000
00424848 0000e798 00000010 00000000 00000000 --> 00420000
00433000 0002d000 00000000 00000000 00000002 --> 00420000
00740020 00001000 00000020 00000040 00000024 --> 00740000
20750020 00001000 00000020 00000040 00000024 --> 20750000
52580020 00001000 00000020 00000040 00000024 --> 52580000
40760020 00001000 00000020 00000040 00000024 --> 40760000
HeapWalk: 259

可以看出,小分配紧密排列,但大分配都在单独的虚拟地址分配中。未使用单独分配中的可用空间。此外,只有主虚拟地址分配有任何标记为可用空间的堆块(标志等于0或2)。