分配自定义内存对齐的内存块的aligned_malloc实现分析

发布于:2024-06-24 ⋅ 阅读:(149) ⋅ 点赞:(0)

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的内存块,


网站公告

今日签到

点亮在社区的每一天
去签到