C ++新的内存分配碎片

时间:2010-04-27 20:14:11

标签: c++ new-operator dynamic-memory-allocation

我试图查看新分配器的行为以及为什么它不会连续放置数据。

我的代码:

struct ci {
    char c;
    int i;
}

template <typename T>
void memTest()
{
    T * pLast = new T();
    for(int i = 0; i < 20; ++i) {
         T * pNew = new T();
         cout << (pNew - pLast) << " ";
         pLast = pNew;
    }
}

所以我用char,int,ci运行它。大多数分配是从最后一个固定长度,有时从一个可用块到另一个块有奇怪的跳跃。

sizeof(char):1
平均跳转:64字节

sizeof(int):4
平均跳跃:16

sizeof(ci):8(int必须放在4字节对齐上)
平均跳跃:9

任何人都可以解释为什么分配器像这样分段内存?另外,为什么char的跳转比int和包含int和char的结构大得多。

6 个答案:

答案 0 :(得分:10)

有两个问题:

  • 大多数分配器在块开始之前存储一些额外的数据(通常是块大小和几个指针)

  • 通常有对齐要求 - 现代操作系统通常会分配至少8个字节的边界。

所以你几乎总是会在连续分配之间找到某种差距。

当然,你不应该依赖任何特定的行为来做这样的事情,实现可以随意做。

答案 1 :(得分:6)

您的代码包含一个错误,要知道指针的距离(char *),否则增量的大小为(T)。

答案 2 :(得分:3)

这不是碎片,它只是将您的分配大小四舍五入到圆块大小。

在一般编程中,您不应该注意由new等通用分配器返回的内存地址模式。当你关心分配行为时,你应该总是使用一个特殊用途的分配器(boost :: pool,你自己编写的东西等)。

例外情况是,如果您正在研究分配器,在这种情况下,您可能会为简单的分配器获取K&R的副本,这可能有助于您了解new如何获取其内存。< / p>

答案 3 :(得分:2)

通常,您不能依赖特定的内存位置。内存分配器的内部簿记数据和对齐要求都会影响块的放置。不需要连续分配块。

此外,某些系统会给你甚至“陌生”的行为。许多现代Linux系统都启用了堆随机化,其中新分配的虚拟内存页面放置在随机地址中,以使某些类型的安全漏洞利用更加困难。对于虚拟内存,不同的已分配块地址并不一定意味着物理内存是碎片化的,因为不需要虚拟地址空间密集。

答案 4 :(得分:0)

正如其他人已经说过的那样,基本上你无法控制内存管理系统的工作方式。 如果您分配了许多单个对象,这可能会导致碎片,并且您无法对此做任何事情。

但是,如果您需要对象在内存中的连续顺序,您可以编写自己的内存分配器,它在malloc()之上运行或者new。控制碎片的一种方法是分配更大的内存块,然后使用placement new (链接到C ++ FAQ Lite)在此块中构建实际的单个对象。

(当然这是有效的,因为对malloc()new T[]的调用可以保证返回一个连续的内存块。一个单独的已分配对象不能被分段;碎片只会导致多个已分配的对象。 )

答案 5 :(得分:0)

对于小分配,boost有一个我用过的非常简单的分配器叫做boost :: simple_segregated_storage

它创建了一个自由和使用过的块的副本,大小相同。只要你只分配它的设置块大小,就不会产生外部碎片(如果你的块大小大于请求的大小,你可能会得到一些内部碎片。)如果你在这里使用它,它也会运行O(1)方式。非常适合小型分配,其中模板编程很常见。