对齐malloc实现的解释

这不是作业,这纯粹是为了我自己的个人教育。

我无法弄清楚如何实现一个对齐的malloc所以在网上查找并找到了这个网站 。 为方便阅读,我将发布以下代码:

#include  #include  void* aligned_malloc(size_t required_bytes, size_t alignment) { void* p1; // original block void** p2; // aligned block int offset = alignment - 1 + sizeof(void*); if ((p1 = (void*)malloc(required_bytes + offset)) == NULL) { return NULL; } p2 = (void**)(((size_t)(p1) + offset) & ~(alignment - 1)); p2[-1] = p1; return p2; } void aligned_free(void *p) { free(((void**)p)[-1]); } void main (int argc, char *argv[]) { char **endptr; int *p = aligned_malloc (100, strtol(argv[1], endptr, 10)); printf ("%s: %p\n", argv[1], p); aligned_free (p); } 

实现确实有效,但老实说我无法弄清楚它是如何工作的。

这是我无法理解的:

  1. 我们为什么需要抵消?
  2. 什么与~(alignment - 1)完成
  3. p2是双指针。 为什么我们可以从一个应该只返回一个指针的函数返回它?
  4. 解决这个问题的一般方法是什么?

任何帮助都非常感谢。

编辑

这与如何仅使用标准库分配对齐内存不重复? 因为我还需要知道如何释放对齐的记忆。

  1. 如果要支持比系统的malloc()对齐,则需要偏移量。 例如,如果您的系统malloc()与8字节边界对齐,并且您想要对齐16个字节,则需要额外15个字节,因此您确定可以将结果移位以根据请求对齐它。 您还可以将sizeof(void*)添加到传递给malloc()的大小,以便为记账留出空间。

  2. ~(alignment - 1)是保证对齐的原因。 例如,如果alignment是16,那么减去1得到15,也就是0xF,然后否定它会产生0xFF..FF0,这是你需要满足来自malloc()任何返回指针的对齐所需的掩码。 请注意,这个技巧假设对齐是2的幂(实际上它通常是这样,但确实应该检查)。

  3. 这是一个void** 。 该函数返回void* 。 这是可以的,因为指向void的指针是“指向任何类型的指针”,在这种情况下,该类型为void* 。 换句话说,允许将void*转换为其他指针类型和从其他指针类型转换,并且双指针仍然是指针。

  4. 这里的总体方案是将原始指针存储在返回给调用者的指针之前。 标准malloc()一些实现做同样的事情:在返回的块之前存储簿记信息。 这使得在调用free()时很容易知道要回收多少空间。

总而言之,这种事情通常没有用,因为标准的malloc()返回系统上最大的对齐方式。 如果您需要在此之外进行对齐,可能还有其他解决方案,包括特定于编译器的属性。

实施确实有效

或许,但我不太确定。 IMO你最好从第一原则开始工作。 马上蝙蝠,

 p1 = (void*)malloc 

是一面红旗。 malloc返回void 。 在C中,任何指针都可以从void *指定。 从malloc通常被认为是不好的forms,因为它具有的任何效果都只能是坏的。

我们为什么需要抵消

偏移量提供了空间来存储malloc返回的指针,稍后由free

malloc检索p1 。 之后,它必须提供free发布。 aligned_mallocp1保留sizeof(void*)个字节,在那里存入p1 ,并返回p2p1指向的块中的第一个“对齐”地址)。 稍后,当调用者将p2传递给aligned_free ,它会将p2实际转换为void *p2[] ,并使用-1作为索引来获取原始p1

什么与〜(对齐-1)完成

这就是将p2放在边界上的原因。 说对齐是16; alignment -1是15,0xF。 ~OxF是除最后4之外的所有位。对于任何指针PP & ~0xF将是16的倍数。

p2是双指针。

指针schmointermalloc返回void* 。 这是一块记忆; 你按照自己的意愿来解决它。 你不会眨眼

 char **args = calloc(7, sizeof(char*)); 

分配7个char *指针的数组,不是吗? 代码从p1中选择至少sizeof(void*)字节的一些“对齐”位置,并且为了free的目的,将其视为void **

什么是一般方法

没有一个答案。 最好的可能是使用标准(或流行)库。 如果你在malloc构建,分配足够的东西来保持“真正的”指针并返回一个对齐的指针是非常标准的,尽管我会以不同的方式编写代码。 syscall mmap返回一个页面对齐的指针,它将满足“aligned”的大多数条件。 根据需要,这可能比在malloc搭载更好或更差。

我对这段代码有一些问题。 我把它们编译成下面的列表:

  1. p1 = (void*)malloc您没有p1 = (void*)malloc的返回值。
  2. free(((void**)p)[-1]); 你不是免费的。
  3. if ((p1 = (void*)malloc(required_bytes + offset)) == NULL)不要在if语句的比较中放置赋值。 我知道很多人这样做,但在我看来,这只是一种糟糕的forms,使代码更难以阅读。

他们在这里做的是将原始指针存储在已分配的块中。 这意味着只有对齐的指针才会返回给用户。 用户从未看到的malloc返回的实际指针。 您必须保留该指针,因为free需要它将块与已分配的列表取消链接并将其放在空闲列表中。 在每个内存块的头部,malloc在那里提供一些内务处理信息。 事情和下一个/ prev指针,大小,分配状态等…. malloc的一些调试版本使用保护字来检查缓冲区是否有溢出的东西。 传递给例程的对齐必须是2的幂。

当我编写自己的malloc版本以便在池内存分配器中使用时,我使用的最小块大小为8个字节。 因此,包括32位系统的标头,总数为28个字节(标头为20个字节)。 在64位系统上,它是40个字节(标头为32个字节)。 当数据与某个地址值(现代计算机系统上的4或8个字节)对齐时,大多数系统都具有更高的性能。 这样做的原因是,如果对齐,机器可以在一个总线周期内抓取整个字。 如果没有,那么它需要两个总线周期来获取整个单词,然后它必须构造它。 这就是编译器在4或8字节上对齐变量的原因。 这意味着地址总线的最后2位或3位为零。

我知道有一些硬件限制需要比默认的4或8更多的对齐。如果我没记错的话,Nvidia的CUDA系统要求事物对齐到256字节……这就是硬件要求。

之前有人问过这个问题。 请参阅: 如何仅使用标准库分配对齐的内存?

希望这可以帮助。