最近用了别人写的一个FFT硬件加速模块,要求DDR中的数据对齐4k边界,估计这个模块用的数据总线是AXI协议的,AXI的突发传输不能超过4k边界。所用的平台是C6678DSP,编译器支持的C++版本是C++98,没有aligned new,所以只能自己写aligned_malloc函数。

额外空间开销

  C++的new本质上调用的是malloc函数,malloc可以在堆区申请一定大小的空间,然后返回这段空间的首地址;与之对应的free函数可以释放已分配的堆区空间。
  如上图所示,在调用malloc后返回指针pp,B表示对齐的边界,对齐的字节数是L。一般来说我们得到的pp指针指向的地址并不是对齐的。但我们可以多分配一点空间,使得数据存放到对齐的位置。那么这样做的话,最糟糕的情况下的额外开销就是L-1,此时pp指针分配到了“边界+1”的位置处。
  从分配的地址如何计算第一个对齐的地址。只需要在分配的地址上加上L-1,再把地址低位清零即可。比如要对齐4k边界:

addraligned=(p+0xFFF)&(0xFFF){ {addr_{aligned} = (p + \rm{0xFFF})\& \sim(\rm{0xFFF})} }

  这样如果pp已经对齐到边界,那么结果就会是pp;而pp如果没有对齐到边界,那么结果就会是当前地址往上的第一个对齐的地址。这个对齐的地址就可以作为aligned_malloc函数的返回值。

空间释放

  
  为了正确释放刚刚分配的空间,我们还需要把,malloc实际分配得到的地址pp保存下来。pp是void*类型,所以我们还需要一个sizeof(void*)大小的空间用来存放pp。这个存放的位置可以就放在对齐的地址的下面。

  这个时候最糟糕的情况还是需要额外的L-1字节的空间,malloc分配得到的地址位于“边界-(sizeof(void*)-1)”的位置。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
}