malloc一般使用当前平台默认的最大内存对齐数对齐内存,如MSVC在32bit下一般是8bit对齐;64位下则是16bit。这样对常规的数据来说没有问题。但如果我们自定义的内存对齐超出了这个范围,则不能直接使用malloc。当我们要分配一块具有特定内存对齐的内存块时,MSVC使用_aligned_malloc,而gcc使用memalign等函数。
《深入C++11 代码优化与工程应用》中给了自己实现aligned_malloc的方法,有些难以理解:
inline void* aligned_malloc(size_t size, size_t alignment)
{
assert(!(alignment & (alignment - 1)));
size_t offset = sizeof(void*) + (--alignment);
cout << "sizeof(void*): " << sizeof(void*) << endl;
char* reqBlockAddr = static_cast<char*>(malloc(offset + size));
if (!reqBlockAddr) return nullptr;
void* alignedAddr = reinterpret_cast<void*>(reinterpret_cast<size_t>(reqBlockAddr + offset) & (~alignment));
cout << "alignedAddr: " << alignedAddr << endl;
auto reqBlockAddrAddr = (void**)((size_t)alignedAddr - sizeof(void*));
cout << "reqBlockAddrAddr: " << reqBlockAddrAddr << endl;
*reqBlockAddrAddr = reqBlockAddr;
cout << "reqBlockAddr: " << *reqBlockAddrAddr << endl;
auto ori = static_cast<void**>(alignedAddr)[-1]; //ori == reqBlockAddr
cout << "ori: " << ori << endl;
//alignedAddr - reqBlockAddr = 4byte (32bit)
//alignedAddr - reqBlockAddrAddr = 1byte (8bit)
return alignedAddr;
}
inline void aligned_free(void* p)
{
free(static_cast<void**>(p)[-1]);
}
int main(int argc, const char * argv[]) {
aligned_malloc(100, 32);
return 0;
}
该程序运行结果是:
其中参数size为要分配的内存块的大小,alignment为内存对齐的粒度,我们分别传入100,32。
先看第一句:!(alignment & (alignment - 1))表示对齐值必须是2^n:如果alignment是2^n,则alignment & (alignment - 1)必为0。因为2^n的二进制是100..(省略号表示后面都是省略号前的值,如0,下同),而2^n-1二进制是011...,两者进行与运算结果必为0,取反结果就是1了。
然后size_t offset = sizeof(void*) + (--alignment)是何意?sizeof(void*)是一个裸指针的大小,一般平台是1byte,其实这是个额外备用指针,先按下不表;而--alignment对于32来说是31,这个31的用意是:不论我需要的内存块多大,由于有了这个31byte的余量,我总能在这个0~31byte余量内找到内存对齐,即32byte的整数倍的内存的位置。
有了上面的offset,于是我们用malloc申请到的实际内存块大小是31+8+100=139byte,它的起始地址是reqBlockAddr。
但是reqBlockAddr是我们内部维护的整个内存块,并不是对外给用户提供的内存对齐到32byte的内存啊?于是通过运算reinterpret_cast<size_t>(reqBlockAddr + offset) & (~alignment),注意这里的alignment已经自减了(如31),结合刚刚所说31的意义,(reqBlockAddr + offset)即(reqBlockAddr + sizeof(void*) + (--alignment))这个地址不但剔除了个额外备用指针,还把31个byte的余量都剔除了,该地址到内存块末尾正好是我们申请的内存100byte。我们需要的是大于等于100byte的内存块,