如何调整VirtualAlloc分配的区域的大小?

时间:2011-09-21 18:04:17

标签: c winapi

我想调整由MS窗口的VirtualAlloc分配的内存区域。查看VirtualFree文档,可以仅部分地解除区域,但不可能部分释放它。也就是说,可以释放部分物理内存,但不能释放虚拟内存的一部分。

我知道在这种情况下可能需要重新分配该区域。但是,复制整个区域的效率会相当低。有没有办法让windows分配一个不同大小的新区域,指向同一个内存?

3 个答案:

答案 0 :(得分:3)

正如您所提到的,由于VirtualFree() documentation状态,似乎无法部分释放一系列保留页面:

  

如果 dwFreeType 参数是MEM_RELEASE,[ lpAddress ]必须是VirtualAlloc函数在页面区域[保留]时返回的基址

以及:

  

如果 dwFreeType 参数是MEM_RELEASE,[ dwSize ]必须为0(零)。

VirtualFree()本身就是内核函数NtFreeVirtualMemory()的瘦包装器。 Its documentation page(与ZwFreeVirtualMemory()相同)也有这样的措辞。

一种可能的解决方法是将多个较小的预留分开。例如,假设您通常一次保留8 MiB的虚拟地址空间。您可以尝试在32个连续的256 KiB预留中保留范围。第一个256 KiB保留包含一个32位无符号位字段,如果 i th,则 i th 位置1 获得256 KiB预留:

#define NOMINMAX
#include <windows.h>
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#define RESERVATION_SIZE (256*1024)

typedef struct st_first_reservation {
    size_t reservation_size;
    uint32_t rfield;
    char premaining[0];
} st_first_reservation;

int main()
{
    SYSTEM_INFO sys_info = { 0 };
    GetSystemInfo(&sys_info);

    assert((RESERVATION_SIZE % sys_info.dwPageSize) == 0);

    void *vp = VirtualAlloc(NULL, 32*RESERVATION_SIZE, MEM_RESERVE, PAGE_NOACCESS);
    if (VirtualFree(vp, 0, MEM_RELEASE) == 0) {
        fprintf(stderr, "Error: VirtualFree() failed.\n");
        return EXIT_FAILURE;
    }

    st_first_reservation *pfirst_reservation = (st_first_reservation *) VirtualAlloc(vp, RESERVATION_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    if (pfirst_reservation == NULL) {
        pfirst_reservation = (st_first_reservation *) VirtualAlloc(NULL, RESERVATION_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
        if (pfirst_reservation == NULL) {
            fprintf(stderr, "Error: VirtualAlloc() failed.\n");
            return EXIT_FAILURE;
        }
    }

    fprintf(stderr, "pfirst_reservation = 0x%p\n", (void *) pfirst_reservation);

    pfirst_reservation->reservation_size = RESERVATION_SIZE;
    pfirst_reservation->rfield = 1LU;

    char *p = (char *) pfirst_reservation;
    unsigned i = 1;
    for (; i < 32; ++i) {
        vp = VirtualAlloc(p += RESERVATION_SIZE, RESERVATION_SIZE, MEM_RESERVE, PAGE_NOACCESS);
        if (vp != NULL) {
            assert(((void *) vp) == p);
            pfirst_reservation->rfield |= 1LU << i;
            fprintf(stderr, "Obtained reservation #%u\n", i + 1);
        } else {
            fprintf(stderr, "Failed to obtain reservation #%u\n", i + 1);
        }
    }

    fprintf(stderr, "pfirst_reservation->rfield = 0x%08x\n", pfirst_reservation->rfield);

    return EXIT_SUCCESS;
}

示例输出:

pfirst_reservation = 0x009A0000
Obtained reservation #2
Obtained reservation #3
Obtained reservation #4
Obtained reservation #5
Obtained reservation #6
Obtained reservation #7
Obtained reservation #8
Obtained reservation #9
Obtained reservation #10
Obtained reservation #11
Obtained reservation #12
Obtained reservation #13
Obtained reservation #14
Obtained reservation #15
Obtained reservation #16
Obtained reservation #17
Obtained reservation #18
Obtained reservation #19
Obtained reservation #20
Obtained reservation #21
Obtained reservation #22
Obtained reservation #23
Obtained reservation #24
Obtained reservation #25
Obtained reservation #26
Obtained reservation #27
Obtained reservation #28
Obtained reservation #29
Obtained reservation #30
Obtained reservation #31
Obtained reservation #32
pfirst_reservation->rfield = 0xffffffff

编辑:我发现,一次性“预先保留”32个256 KiB范围,免费,然后尝试重新预订,就像你一样多可以。

我更新了上面的代码和示例输出。

在多线程环境中,代码可能会回退到第一个预留的“任何地方”分配。也许最好尝试将RESERVATION_SIZE个字节保留在保留 - 然后释放的32*RESERVATION_SIZE字节范围内五次左右,最后再回到“任何地方”分配。

答案 1 :(得分:1)

不是答案,但我不得不问:

考虑到您所处的痛苦,VirtualAlloc()的性能命中率以及代码的不可移植性;与VIrtualAlloc()给出的任何值相比,您是否可以考虑使用malloc()和朋友? IOW,VirtualAlloc()是否具有任何真正的优势?

在我看来(可能我的观点),malloc()的强大功能和普遍性超过了VirtualAlloc()所承诺的任何吸引力。它会让你更直接地处理你的地区。

很抱歉没有回答。当人们问“谁曾经甚至认为 时,我讨厌它?”当然,当 那个问“为什么”的人时会有所不同:-)

答案 2 :(得分:1)

如果要缩小分配,可以在分配的子范围内使用VirtualFreeMEM_DECOMMIT。请注意,这不会释放地址空间;只有物理RAM。如果您希望增长它,可以尝试VirtualAlloc在现有分配后立即传递地址。当然,这可能会失败,此时您需要复制内存。

您还可以尝试将GlobalAllocGMEM_MOVEABLEGlobalReAlloc(或等效的Heap *功能)一起使用。

如果需要释放地址空间,可能需要尝试使用匿名内存映射对象,并在运行时更改其映射窗口 - 或者只是使用64位来获取额外的地址空间。