对齐malloc实现的解释

时间:2016-06-29 01:13:44

标签: c pointers malloc

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

我无法弄清楚如何实现对齐的malloc,所以在网上看了this website。为方便阅读,我将发布以下代码:

#include <stdlib.h>
#include <stdio.h>

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. 解决此问题的一般方法是什么?
  5. 非常感谢任何帮助。

    修改

    这不是How to allocate aligned memory only using the standard library?的副本,因为我还需要知道如何释放对齐的内存。

4 个答案:

答案 0 :(得分:10)

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

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

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

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

  5. 所有这一切,通常没有用,因为标准malloc()返回系统上最大的对齐方式。如果您需要超出该范围,可能还有其他解决方案,包括特定于编译器的属性。

答案 1 :(得分:2)

  

实施确实有效

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

p1 = (void*)malloc

是一面红旗。 malloc返回void。在C中,可以从void *分配任何指针。来自malloc的投射通常被认为是不好的形式,因为它具有的任何效果都只会很糟糕。

  

为什么我们需要一个偏移量

偏移量为隐藏malloc返回的指针提供了空间,稍后由free使用。

p1检索

malloc。之后,必须提供给free才能被释放。 aligned_mallocsizeof(void*)保留p1个字节,在那里隐藏p1,然后返回p2p1块中的第一个“对齐”地址指着)。稍后,当调用者将p2传递给aligned_free时,它会将p2转换为void *p2[],并使用-1作为索引获取原始p1

  

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

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

  

p2是双指针。

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

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

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

  

一般方法是什么

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

答案 2 :(得分:0)

我对此代码有一些问题。我把它们编成了以下列表:

  1. p1 = (void*)malloc您没有强制转换malloc的返回值。
  2. free(((void**)p)[-1]);你没有自由投射。
  3. if ((p1 = (void*)malloc(required_bytes + offset)) == NULL)不要在if语句的比较中放置一个赋值。我知道很多人这样做,但在我看来,这只是一种糟糕的形式,使代码更难以阅读。
  4. 他们在这里做的是将原始指针存储在已分配的块中。这意味着只有对齐的指针才会返回给用户。用户从未看到的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字节......这就是硬件要求。

    之前有人问过这个问题。请参阅:How to allocate aligned memory only using the standard library?

    希望这有帮助。

答案 3 :(得分:0)

假设我们需要SZ字节的对齐内存,让:

A is the alignment.
W is the CPU word size.
P is the memory returned by malloc

我们将返回(P + Y),其中(P + Y)mod A = 0

因此,我们应该保存原始指针 P 以便以后释放内存。 在这种情况下,我们应该分配(SZ + W)个字节,但是为了使内存对齐,我们将构造 Z个字节,其中(P%A = Z )=>(Z∈[0,A-1])

So the total memory to be allocated is:  SZ + W + MAX(Z) = SZ + W + A - 1

要返回的指针为 P + Y = P + W + MAX(Z)-(P + W + MAX(Z))mod A

我们拥有:X-X mod A = INT(X / A)* A = X&〜(A-1)

因此我们可以将 P + W + MAX(Z)-(P + W + MAX(Z))mod A 替换为(P + W + MAX(Z))& 〜(A-1)

The memory to be returned is: (P + W + MAX(Z)) & ~(A - 1)