我试图弄清楚在分配失败之前我可以分配多少内存。
这个简单的C ++代码分配一个缓冲区(大小为1024字节),分配缓冲区的最后五个字符,报告,然后删除缓冲区。然后它将缓冲区的大小加倍并重复,直到它失败。
除非我遗漏了某些内容,否则代码能够在我的MacBook Pro上出现故障之前分配高达65 TB的内存。这甚至可能吗?它如何分配比我在机器上更多的内存?我必须遗漏一些简单的东西。
int main(int argc, char *argv[])
{
long long size=1024;
long cnt=0;
while (true)
{
char *buffer = new char[size];
// Assume the alloc succeeded. We are looking for the failure after all.
// Try to write to the allocated memory, may fail
buffer[size-5] = 'T';
buffer[size-4] = 'e';
buffer[size-3] = 's';
buffer[size-2] = 't';
buffer[size-1] = '\0';
// report
if (cnt<10)
cout << "size[" << cnt << "]: " << (size/1024.) << "Kb ";
else if (cnt<20)
cout << "size[" << cnt << "]: " << (size/1024./1024.) << "Mb ";
else
cout << "size[" << cnt << "]: " << (size/1024./1024./1024.) << "Gi ";
cout << "addr: 0x" << (long)buffer << " ";
cout << "str: " << &buffer[size-5] << "\n";
// cleanup
delete [] buffer;
// double size and continue
size *= 2;
cnt++;
}
return 0;
}
答案 0 :(得分:44)
当您要求内存时,操作系统保留在您实际使用内存之前不实际提供您的内存的权利。
这里发生了什么:你只使用了5个字节。我20世纪80年代的ZX81可以解决这个问题。
答案 1 :(得分:36)
与几乎所有现代操作系统一样,MacOS X使用&#34;延迟分配&#34;为了记忆。当您致电new
时,操作系统实际上并没有分配任何内存。它只是记下你的程序需要一定量的内存,并且你想要的内存区域从某个地址开始。内存仅在程序尝试使用时实际分配。
此外,内存以名为&#34; pages&#34;的单位分配。我相信MacOS X使用4kb页面,所以当你的程序写入缓冲区的末尾时,操作系统会给你4096个字节,同时保留缓冲区的其余部分只是一个&#34;你的程序想要这个内存&#34;注意:
至于你为什么要达到64TB的限制,这是因为当前的x86-64处理器使用48位寻址。这提供了256 TB的地址空间,可在操作系统和程序之间平均分配。加倍64 TB的分配将完全适合你的程序的128 TB地址空间的一半,除了程序已经占用了一点点。
答案 2 :(得分:7)
Virtual memory是分配比物理RAM +交换空间更多地址空间的关键。
malloc使用mmap(MAP_ANONYMOUS)
系统调用从操作系统获取页面。 (假设OS X的工作方式与Linux类似,因为它们都是POSIX操作系统)。这些页面都是写入时复制映射到单个物理零页面。即只有TLB未命中(没有页面错误且没有物理RAM分配)它们都读为零。 x86页面是4kiB。 (我没有提到大页面,因为它们在这里不相关)。
写入任何这些页面会触发内核的软页面错误,以处理写时复制。内核分配物理内存的归零页面并重新连接该物理页面支持的虚拟页面。从页面错误返回时,存储将重新执行并且此时成功。
因此,在分配64TiB并将5个字节存储到其末尾之后,您已经使用了一个额外的物理内存页。 (并且在malloc的簿记数据中添加了一个条目,但这可能已经分配并且在一个脏页面中。在类似的关于多个微小分配的问题中,malloc's bookkeeping data was what eventually used up all the space)。
如果你真的弄脏了比系统RAM + swap更多的页面,那么内核就会出现问题,因为malloc
返回NULL为时已晚。这称为"overcommit",某些操作系统默认启用它,而其他操作系统则不支持。在Linux中,它是可配置的。
正如Mark解释的那样,你在64TiB上失去了动力,因为当前的x86-64实现只支持48位虚拟地址。高16位需要是第47位的副本。(例如,如果64位值是低48位的符号扩展,则地址只是规范的。)
这个要求阻止程序用高位“巧妙”做任何事情,然后打破未来支持更大虚拟地址空间的硬件。