4

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

我的代码:

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 的结构大得多。

4

5 回答 5

10

有两个问题:

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

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

因此,您几乎总是会在连续分配之间出现某种差距。

当然,你永远不应该依赖任何特定的行为来实现这样的事情,在这种情况下,实现可以随心所欲地进行。

于 2010-04-27T20:22:27.080 回答
6

您的代码包含一个错误,以了解将它们转换为 (char *) 的指针的距离,否则增量位于 sizeof(T) 中。

于 2010-04-27T20:23:24.103 回答
3

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

在一般编程中,您不应该注意由通用分配器(如new. 当您确实关心分配行为时,您应该始终使用特殊用途的分配器(boost::pool,您自己编写的东西等)

例外情况是,如果您正在研究分配器,在这种情况下,您可能会比拿起您的K&R副本来获得一个简单的分配器更糟糕,这可能会帮助您了解如何new获取它的内存。

于 2010-04-27T20:47:34.920 回答
2

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

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

于 2010-04-27T20:28:06.250 回答
0

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

它创建大小相同的空闲块和已用块的副本。只要你只分配给它设置的块大小,你就不会得到外部碎片(尽管如果你的块大小大于请求的大小,你会得到一些内部碎片。)如果你在这个中使用它,它也会运行 O(1)方式。非常适合模板编程中常见的小分配。

于 2010-04-27T21:11:38.783 回答