在分配更大的空间时,为什么堆地址的增长方向会变得相反?

时间:2013-04-16 02:16:59

标签: c++ c memory operating-system

我正在对堆地址增长做一些实验,并且发生了一些有趣的事情。 (OS:CentOS,)

但我不明白,为什么会这样?谢谢!

这是我先做的事情:

double *ptr[1000];
for (int i=0;i<1000;i++){
    ptr[i] = new double[**10000**];
    cout << ptr[i] << endl;
}

输出是增量的(对于最后几行):

....
....
0x2481be0
0x2495470
0x24a8d00
0x24bc590
0x24cfe20
0x24e36b0
0x24f6f40
0x250a7d0
0x251e060

然后我将10000改为20000:

double *ptr[1000];
for (int i=0;i<1000;i++){
    ptr[i] = new double[**20000**];
    cout << ptr[i] << endl;
}

地址变得更像堆栈空间的地址(和递减):

....
....
0x7f69c4d8a010
0x7f69c4d62010
0x7f69c4d3a010
0x7f69c4d12010
0x7f69c4cea010
0x7f69c4cc2010
0x7f69c4c9a010
0x7f69c4c72010
0x7f69c4c4a010
0x7f69c4c22010
0x7f69c4bfa010
0x7f69c4bd2010
0x7f69c4baa010
0x7f69c4b82010

5 个答案:

答案 0 :(得分:7)

不同的环境/实现使用不同的策略分配内存,因此没有一个正确的规则。但是,常见的模式是对小对象与大对象使用不同的分配策略。

通常,运行时将为不同大小的对象提供多个堆,这些对象针对不同的使用模式进行了优化。例如,小对象往往经常被分配并快速删除,而大对象往往很少创建并且寿命很长。

如果你为一切使用单个堆,那么一些小对象将在你的整个内存空间中快速填充,留下许多中等大小的块可用,但较大的对象需要很少或没有大块。这被称为内存碎片,即使名义上你的应用程序有大量可用内存,也可能导致您的分配失败。

使用不同堆的另一个原因是对不同的对象大小使用不同的使用跟踪方法。例如,实现可能会从操作系统请求大型对象的新内存块,对于小型对象,请使用一些较小的OS内存块和C运行时堆管理器处理的子分配。对大型对象非常有效的内存使用跟踪机制对于较小的对象来说可能非常昂贵,因为用于跟踪使用的内存成为每个对象使用的实际内存的重要部分。

在你的情况下,我的猜测是运行时在内存空间的开头分配小对象,从下到上分配较大的对象,自上而下,以避免碎片。

答案 1 :(得分:4)

你不会在这里得到一个好的答案,因为新函数可以选择它想要分配内存的任何方法。我的猜测是,这里的算法将池分成小型和大型分配池,大型分配池向下增长,因此它们可以在中间相遇(以免浪费任何空间)。

答案 2 :(得分:0)

在UNIX上,分配器使用sbrk(2)和mmap(2)从操作系统获取内存。 sbrk返回的地址定义明确,但mmap的地址是&#34;无论什么可用&#34;。在Windows上,分配器使用VirtualAlloc(),类似于mmap。

答案 3 :(得分:0)

实现可以自由地使用不同分配方案的混合。在C ++中,存在数千甚至数百万个相对较小的对象是正常的,因此对于库的内存分配例程来说,确保它们打包并且非常轻量级是有意义的。您对10000个双打的分配是这样的:它们相隔80016个字节 - 80000个用于10000个8字节变量,只有16个字节填充。特别规定节点大小与2的幂无关,而当分配20000倍时,它们每次递减163840字节......奇怪的是,正好是10 * 2 ^ 14。这告诉我,前一个分配是通过一个用于支持C ++分配new函数的有效小对象分配的堆来满足的,而后者已经过了一个并且可能被发送到{{1}来自不同堆的内存,浪费更多。

答案 4 :(得分:0)

你很幸运,10000个双打和20000个双打的大小恰好位于一个名为MMAP_THRESHOLD的关键阈值的两侧。

MMAP_THRESHOLD默认为128KB。因此,80KB(即10000个双倍)的mem alloc请求在堆上进行服务,而160KB(20000个双精度)的mem alloc请求由匿名内存映射(通过mmap sys调用)提供服务。 (请注意,对于大型mem alloc使用mem映射可能会因其不同的底层内存分配处理机制而产生额外的惩罚。您可能需要调整MMAP_THRESHOLD以获得应用程序的最佳性能。)

Linux Man for malloc中:

  

通常,malloc()从堆中分配内存,并根据需要使用sbrk(2)调整堆的大小。当分配大于MMAP_THRESHOLD字节的内存块时,glibc malloc()实现使用mmap(2)将内存分配为私有匿名映射。 MMAP_THRESHOLD默认为128 kB,但可以使用mallopt(3)进行调整。使用mmap(2)执行的分配不受RLIMIT_DATA资源限制的影响(请参阅getrlimit(2))。