最近用了别人写的一个FFT硬件加速模块,要求DDR中的数据对齐4k边界,估计这个模块用的数据总线是AXI协议的,AXI的突发传输不能超过4k边界。所用的平台是C6678DSP,编译器支持的C++版本是C++98,没有aligned new,所以只能自己写aligned_malloc函数。
- 20220915更新
才知道有一个stdlib.h里有一个memalign,可以用来在堆区开辟对齐的堆区空间,对齐的地址只能是2的整数次幂。不过用起来也很方便,那样就不用自己写aligned_malloc和aligned_free了。由memalign分配的空间可以用free来释放。
额外空间开销
C++的new本质上调用的是malloc函数,malloc可以在堆区申请一定大小的空间,然后返回这段空间的首地址;与之对应的free函数可以释放已分配的堆区空间。
如上图所示,在调用malloc后返回指针
p
p
p,B表示对齐的边界,对齐的字节数是L。一般来说我们得到的
p
p
p指针指向的地址并不是对齐的。但我们可以多分配一点空间,使得数据存放到对齐的位置。那么这样做的话,最糟糕的情况下的额外开销就是L-1,此时
p
p
p指针分配到了“边界+1”的位置处。
从分配的地址如何计算第一个对齐的地址。只需要在分配的地址上加上L-1,再把地址低位清零即可。比如要对齐4k边界:
a
d
d
r
a
l
i
g
n
e
d
=
(
p
+
0
x
F
F
F
)
&
∼
(
0
x
F
F
F
)
{{addr_{aligned} = (p + \rm{0xFFF})\& \sim(\rm{0xFFF})}}
addraligned=(p+0xFFF)&∼(0xFFF)
这样如果
p
p
p已经对齐到边界,那么结果就会是
p
p
p;而
p
p
p如果没有对齐到边界,那么结果就会是当前地址往上的第一个对齐的地址。这个对齐的地址就可以作为aligned_malloc函数的返回值。
空间释放
为了正确释放刚刚分配的空间,我们还需要把,malloc实际分配得到的地址
p
p
p保存下来。
p
p
p是void*类型,所以我们还需要一个sizeof(void*)大小的空间用来存放
p
p
p。这个存放的位置可以就放在对齐的地址的下面。
这个时候最糟糕的情况还是需要额外的L-1字节的空间,malloc分配得到的地址位于“边界-(sizeof(void*)-1)”的位置。
示例代码
void *aligned_malloc(size_t num, size_t alignment){
void *pOrig, *pAligned;
size_t offset;
// check alignment is power of 2
if((alignment & (alignment - 1)) != 0) pAligned = NULL;
else{
offset = alignment - 1 + sizeof(void *);
pOrig = malloc(num + offset);
pAligned = (void *)(((size_t)pOrig + (size_t)offset) & (~(alignment-1)));
*((void **)((size_t)pAligned-sizeof(void *))) = pOrig;
}
return pAligned;
}
void aligned_free(void *pAligned){
void **p = (void **)((size_t)pAligned- sizeof(void *));
free(*p);
}