C-此malloc的实现是否是凹凸分配器?

问题描述 投票:1回答:1

我最近写了一个小malloc,想知道它是否是凹凸分配器。我不知道这是因为(如果我错了,请纠正我)我相信实际的malloc(在使用mmap而不是sbrk时)使用的是相同的技术(某种),但是凹凸分配器只会增加堆的位置。这是我的代码:

#include <cstdio>
#include <cstddef>
#include <unistd.h>

#define word_size sizeof(intptr_t)
#define align(n) ((n + word_size - 1) & ~(word_size - 1))

void* my_malloc(size_t size) {
    void* p = sbrk(0);
    if (sbrk(align(size)) == (void*) -1)
        return NULL; // failed
    return p;
}

int main() {
    int* foo = (int*) my_malloc(1);
    *foo = 100;
    printf("%d\n", *foo);
}
c malloc allocator
1个回答
2
投票

所以我以前从没听过术语“凹凸分配器”,但是这个概念很简单。

这是一个非常幼稚的分配器,由于涉及很少的内务处理,所以可以非常快,但是您必须忍受非常严格的约束:没有针对个人请求的“免费”操作-您只需破坏整个事情即可。] >

在您的情况下,您正在调用sbrk(0)以获取程序整个数据段末尾的第一个地址-这将是返回值-然后在经过适当舍入后,用sbrk(nbytes)“撞住”存储器的末尾起来。

这意味着程序的数据空间只是针对每个请求而增长,并且尝试释放某些内容没有任何意义,因为您不能只是在地址空间中插入漏洞(嗯,可能有一些时髦的VM东西会起作用,但是变得复杂)。

static void *bump_arena_start = 0;

void* my_malloc(size_t size) {
    void* p = sbrk(0);

    if (bump_arena_start == 0) bump_arena_start = p;

    if (sbrk(align(size)) == (void*) -1)
        return NULL; // failed
    return p;
}

void destroy_bump_arena(void)
{
    if (bump_arena_start) brk(bump_arena_start);
}

这可能是一个凹凸分配器,但出于多种原因,它会是一个可怕的

首先:它假定nobody else

正在分配内存,它必须重写所有其他操作:mallocC++ new,C运行时中的所有内容,等等。

想象一下,如果您在休息时做自己的事,然后某些other

函数调用sbrk()来分配一些内存,将会发生什么。现在他们在您的竞技场中,但您大多不知道。到目前为止没有问题,但是一旦您破坏了舞台,它就会杀死其他一切。

您实际使用这种方法的方式是,当您有很多微小的分配不想跟踪并且可以一次释放时,因此您可以使用系统分配器(malloc())并要求一个足够大的块来满足您的需求-我们称它为32 KB-并将其填充到代表这个凹凸区域的某个对象中。

您在各处分配了很多小块,执行所需的所有任务,然后释放最初的32 KB块将其全部销毁。

事实是:您必须超级小心

,不要让这些指针之一逃逸到系统的其他部分,因为不允许它们生活在您的竞技场之外。” >

这只是一个非常专业的用例,通常可能没有用,除非您正在执行嵌入式工作(本质上是在控制自己的运行时),否则您将无法通过这种方式真正破坏系统。 >

旁注:如果对象大于整数指针的大小,则可能会遇到对齐问题。如果您这样做了?

   int      *foo1 = my_malloc(sizeof(int));       // 8 bytes (usually)
   __int128 *foo2 = my_malloc(sizeof(__int128));  // 16 bytes

天真的对齐方式会将int放在8字节的边界上,但是128位的值(即16字节)也是如此,而不是与其自身大小对齐;在某些平台上,这可能是一个错误,并且几乎总是效率低下。

要正确执行此操作,您可以通过sbrk(0)查询当前的下一个值,并确保it

的大小正确对齐,可能会使中断点有所增加。

EDIT

:我已经进行了更多研究,很显然,您的示例并没有算作碰撞分配器。这就是为什么。

[内存系统不仅跟踪“下一个”指针,而且跟踪了已执行了多少分配,并且它支持忽略指针值而只减小分配计数器的伪自由操作。

如果分配计数器曾经达到零,这意味着没有其他人拥有该内存,因此它可以通过将中断倒退到初始值来释放所有内容,本质上是从干净的开始。

要正确使用此功能,您必须非常小心自己的释放,而双重释放可能会非常痛苦。

非常有用的参考:https://os.phil-opp.com/allocator-designs/

EDIT2

-有关每个请求的对齐的更多信息。

无论您在哪个平台上,您都必须至少对对齐有一定的了解,因为即使平台允许对内存进行未对齐的访问,它的速度通常总是较慢。

最简单的始终正确的方法是找出平台上支持的最大可能的标量对象,并将其用作对齐模,也许是__int128。如果您总是四舍五入到最接近的16个字节,则几乎不会遇到对齐问题(而且很容易)。

但是它也是空间效率低的:如果您要为两个字节的short分配空间,它将浪费其后的14个字节。在您的应用程序中,这可能没什么大不了的,或者可能是一回事。

我自己从来没有写过内存分配器,所以我在这里做了很多工作,但是任何使用凹凸分配器的人都有一些特殊的要求,并且可以接受特殊的请求。

所以:我可以想象一个分配器不仅需要大小,还需要所需的对齐方式,并且它将使用sbrk(0)指针并将that

向上舍入为所需的对齐方式,并将其保存为返回值,然后调用sbrk(size)碰撞结束标记。

请注意,您并没有调整分配的大小,而只是调整了低级项目的大小:要求一个20个short值的数组意味着您要求的是40个字节,但对齐方式为2,字符串的100个字节表示您只需接下100个字节(不带任何对齐方式)。

void *my_malloc(size_t nbytes, size_t align = 8)
{
    void *p = sbrk(0);
    p += round up but too hard to think on Friday
    sbrk(nbytes);
    num_allocations++;
    return p;
}

这样,如果您不提供对齐尺寸,则会做出与您相同的安全假设,但是您始终可以询问是否要特别。

[再次:我基本上只是在弥补这个问题,我从来不需要这样考虑,但是我确实知道,如果我正在内存受限的平台上工作,例如Arduino,其RAM以< [千字节

,您必须考虑一下。
© www.soinside.com 2019 - 2024. All rights reserved.