我正在尝试查看我可以在Windows中分配内存的最大地址。我通过使用GetSystemInfo
获得最大地址,然后尝试分配一个4KB的页面。我注意到一些我以前没见过的行为,我想知道有没有人能解释一下为什么会这样。首先,这是我的测试代码:
SYSTEM_INFO info;
GetSystemInfo(&info);
PVOID base = (PUCHAR)info.lpMaximumApplicationAddress - info.dwPageSize;
SIZE_T size = info.dwPageSize;
LPVOID mem = VirtualAlloc(base, size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
以下是我注意到的几件事。
- 分配的大小实际上大于一个页面的4KB,它是64KB。为什么?
- 分配的基址为
0x7ffffffe0000
。对于64KB的分配大小,结束地址是7fffffff0000
,它只比最高用户地址多一个字节。这不是越界了吗?
我在x64 Windows上这样做。
如果我能明白为什么会这样,那就太好了。
分配的大小实际上大于一个页面的4KB,它是64KB。为什么?
你没有考虑到info.dwAllocationGranularity
。页面大小可能是4K,但粒度是64K。页面分配必须是粒度的偶数倍。因此,您指定的基址将被舍入到下一个最低粒度边界。这在VirtualAlloc()
文档中有讨论:
lpAddress
要分配的区域的起始地址。如果内存被保留,指定的地址被舍入到分配粒度的最接近的倍数。如果内存已经被保留并且正在提交,则地址四舍五入到下一页边界。要确定页面的大小和主机上的分配粒度,请使用GetSystemInfo函数。如果该参数为NULL,则由系统决定在哪里分配区域。
请参见为什么地址空间分配粒度是64K?
在您的情况下,info.lpMaximumApplicationAddress
可能是0x7FFFFFEFFFF
(比8TB少64K,这是Windows 8和更早版本支持的最大内存;Windows 8.1将限制提升到128TB)。减去4K是0x7FFFFFEEFFF
,然后四舍五入到0x7FFFFFF0000
,这是64K的偶数倍。
分配的基址是0x7ffffffe0000。对于64KB的分配大小,结束地址为7fffffff0000,仅比最高用户地址多一个字节。这不是越界了吗?
info.lpMaximumApplicationAddress
的作用类似于数组的1-past-the-end指针,或标准c++容器的end()
迭代器。这是一个最大的地址,你不应该超过,甚至取消引用。您可以访问内存地址,直到最大地址,但不包括它。