我最近写了一个小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);
}
所以我以前从没听过术语“凹凸分配器”,但是这个概念很简单。
这是一个非常幼稚的分配器,由于涉及很少的内务处理,所以可以非常快,但是您必须忍受非常严格的约束:没有针对个人请求的“免费”操作-您只需破坏整个事情即可。] >
在您的情况下,您正在调用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
正在分配内存,它必须重写所有其他操作:malloc
,C++ 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以< [千字节
,您必须考虑一下。