这不是家庭作业,这纯粹是为了我个人的教育。
我无法弄清楚如何实现对齐的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);
}
实施确实有效,但老实说,我无法弄清楚它是如何运作的。
这是我无法理解的:
~(alignment - 1)
完成的事情p2
是一个双指针。为什么我们可以从一个应该只返回一个指针的函数返回它?非常感谢任何帮助。
修改
这不是How to allocate aligned memory only using the standard library?的副本,因为我还需要知道如何释放对齐的内存。
答案 0 :(得分:10)
如果您想支持超出系统malloc()
范围的对齐,则需要偏移量。例如,如果您的系统malloc()
与8字节边界对齐,并且您想要对齐16个字节,则需要额外15个字节,因此您确定可以移动结果以根据请求对齐它。您还可以将sizeof(void*)
添加到传递给malloc()
的尺寸,以便为记账留出空间。
~(alignment - 1)
是保证对齐的原因。例如,如果对齐为16,则减去1得到15,即0xF,然后否定它会产生0xFF..FF0,这是您需要满足来自malloc()
的任何返回指针的对齐所需的掩码。请注意,这个技巧假定对齐是2的幂(实际上它通常是这样,但确实应该检查)。
这是void**
。该函数返回void*
。这是可以的,因为指向void的指针是“指向任何类型的指针”,在这种情况下,该类型为void*
。换句话说,允许将void*
转换为其他指针类型和从其他指针类型转换,并且双指针仍然是指针。
这里的整体方案是将原始指针存储在返回给调用者的指针之前。标准malloc()
的一些实现做同样的事情:在返回的块之前存储簿记信息。这样可以很容易地知道在调用free()
时要回收多少空间。
所有这一切,通常没有用,因为标准malloc()
返回系统上最大的对齐方式。如果您需要超出该范围,可能还有其他解决方案,包括特定于编译器的属性。
答案 1 :(得分:2)
实施确实有效
也许,但我不太确定。 IMO你最好从第一原则开始工作。马上就好了,
p1 = (void*)malloc
是一面红旗。 malloc
返回void
。在C中,可以从void *
分配任何指针。来自malloc
的投射通常被认为是不好的形式,因为它具有的任何效果都只会很糟糕。
为什么我们需要一个偏移量
偏移量为隐藏malloc
返回的指针提供了空间,稍后由free
使用。
p1
检索 malloc
。之后,必须提供给free
才能被释放。 aligned_malloc
在sizeof(void*)
保留p1
个字节,在那里隐藏p1
,然后返回p2
(p1
块中的第一个“对齐”地址指着)。稍后,当调用者将p2
传递给aligned_free
时,它会将p2
转换为void *p2[]
,并使用-1作为索引获取原始p1
。
和〜(对齐 - 1)完成什么
这是将p2
放在边界上的原因。说对齐是16; alignment -1
是15,0xF。 ~OxF
除了最后一个之外都是位。对于任何指针P
,P & ~0xF
将是16的倍数。
p2
是双指针。
指针 schmointer 。 malloc
返回void*
。这是一块记忆;你按照自己的意愿来解决它。你不会眨眼
char **args = calloc(7, sizeof(char*));
分配7个char *
指针数组,不是吗?该代码从sizeof(void*)
中选择至少p1
个字节的“对齐”位置,并且出于free
的目的,将其视为void **
。
一般方法是什么
没有一个答案。最好的可能是使用标准(或流行)库。如果你在malloc
之上构建,分配足够的东西以保持“真正的”指针并返回一个对齐的指针是非常标准的,尽管我会以不同的方式编码。系统调用mmap
返回页面对齐的指针,它将满足“对齐”的大多数条件。根据需要,这可能比捎带malloc
更好或更差。
答案 2 :(得分:0)
我对此代码有一些问题。我把它们编成了以下列表:
p1 = (void*)malloc
您没有强制转换malloc的返回值。free(((void**)p)[-1]);
你没有自由投射。if ((p1 = (void*)malloc(required_bytes + offset)) == NULL)
不要在if语句的比较中放置一个赋值。我知道很多人这样做,但在我看来,这只是一种糟糕的形式,使代码更难以阅读。他们在这里做的是将原始指针存储在已分配的块中。这意味着只有对齐的指针才会返回给用户。用户从未看到的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)