来自 https://blog.csdn.net/jin739738709/article/details/122992753
什么是内存对齐
什么是[内存对齐]?有两种解释:
- 存放数据的首地址是某个数(通常它为4,8或者32)的倍数。
- 数据结构所占字节数是某个数(通常它为4,8或者32)的倍数。
如何实现内存对齐
图解
多申请空间
为了能够内存对齐而申请的内存大小,会比实际需要的内存多alignment – 1 + pointerSize
- raw是通过malloc返回的地址;
- size是原本申请的空间;
- alignment是对齐量,比如32;
- pointerSize是指针类型占内存的字节数(简单说就是存放一个指针需要多少个字节)
在大空间中"裁剪"出内存对齐的部分并返回
- aligned即为对齐后的首地址了;
- 用二进制10 0000将地址的后五位归零,即可达到对齐的目的
- ~是二进制取反的意思
- &是与的意思
例如:
假设,start = 100, alignment= 32的情况
alignment = (0010 0000)B
alignment - 1 = (0001 1111)B
~(alignment - 1) = (1110 0000)B
size = (0110 0100)B
(size +alignment - 1) = (1000 0011)B
(size +alignment - 1) & ~(alignment - 1) = (1000 0000)B = (128)D
128 / 32 = 4, 这个地址可以对齐32位。
可以尝试不同的start,结果都是一样的,非常有意思的算法。
释放这块地址
- 先前的pointerSize空间排上用场了.在其中存放raw指针,便于我们后续访问到raw并释放他.
- 注意:这里释放地址不是在"裁剪"后就立刻释放的,而是在整个数组需要释放时将所有地址空间都释放.也就是说,例如一个int 44大小的矩阵,他采用了对其内存的方式存储,他的实际占用空间会比44*8字节要大.
*(void**)((uintptr_t)aligned - pointerSize) = raw;
代码部分
代码中的疑问
-
uintptr_r/intptr_t
- 作用:用于跨平台的情况下,不同机器字长(16位、32位、64位)整数与指针相互转移的的通用性
- 存在于c99中的<stdint.h>
/* Types for `void *' pointers. */ #if __WORDSIZE == 64 # ifndef __intptr_t_defined typedef long int intptr_t; # define __intptr_t_defined # endif typedef unsigned long int uintptr_t; #else # ifndef __intptr_t_defined typedef int intptr_t; # define __intptr_t_defined # endif typedef unsigned int uintptr_t; #endif
- 各机器intptr_t与uintptr_t
位数 char short int long 指针 intptr_t 16 1个字节8位 2个字节16位 2个字节16位 4个字节32位 2个字节16位 int 32 1个字节8位 2个字节16位 4个字节32位 4个字节32位 4个字节32位 int 64 1个字节8位 2个字节16位 4个字节32位 8个字节64位 8个字节64位 long int
完整代码
#include<iostream>
void* aligned_malloc(size_t size, int alignment)
{
// 分配足够的内存, 这里的算法很经典, 早期的STL中使用的就是这个算法
// 首先是维护FreeBlock指针占用的内存大小
const int pointerSize = sizeof(void*);
// alignment - 1 + pointerSize这个是FreeBlock内存对齐需要的内存大小
// 前面的例子sizeof(T) = 20, __alignof(T) = 16,
// g_MaxNumberOfObjectsInPool = 1000
// 那么调用本函数就是alignedMalloc(1000 * 20, 16)
// 那么alignment - 1 + pointSize = 19
const int requestedSize = size + alignment - 1 + pointerSize;
// 分配的实际大小就是20000 + 19 = 20019
void* raw = malloc(requestedSize);
// 这里实Pool真正为对象实例分配的内存地址
uintptr_t start = (uintptr_t)raw + pointerSize;
// 向上舍入操作
// 解释一下, __ALIGN - 1指明的是实际内存对齐的粒度
// 例如__ALIGN = 8时, 我们只需要7就可以实际表示8个数(0~7)
// 那么~(__ALIGN - 1)就是进行舍入的粒度
// 我们将(bytes) + __ALIGN-1)就是先进行进位, 然后截断
// 这就保证了我是向上舍入的
// 例如byte = 100, __ALIGN = 8的情况
// ~(__ALIGN - 1) = (1 000)B
// ((bytes) + __ALIGN-1) = (1 101 011)B
// (((bytes) + __ALIGN-1) & ~(__ALIGN - 1)) = (1 101 000 )B = (104)D
// 104 / 8 = 13, 这就实现了向上舍入
// 对于byte刚好满足内存对齐的情况下, 结果保持byte大小不变
// 记得《Hacker's Delight》上面有相关的计算
// 这个表达式与下面给出的等价
// ((((bytes) + _ALIGN - 1) * _ALIGN) / _ALIGN)
// 但是SGI STL使用的方法效率非常高
void* aligned = (void*)((start + alignment - 1) & ~(alignment - 1));
// 这里维护一个指向malloc()真正分配的内存
*(void**)((uintptr_t)aligned - pointerSize) = raw;
// 返回实例对象真正的地址
return aligned;
}
| | | |
| ---- | ---- | ---- |
| | | |
// 这里是内部维护的内存情况
// 这里满足内存对齐要求
// |
// ----------------------------------------------------------------------
// | 内存对齐填充 | 维护的指针 | 对象1 | 对象2 | 对象3 | ...... | 对象n |
// ----------------------------------------------------------------------
// ^ | 指向malloc()分配的地址起点
// | |
// -----------------------
template<typename T>
void aligned_free(T * aligned_ptr) {
if (aligned_ptr)
{
free(((T**)aligned_ptr)[-1]);
}
}
bool isAligned(void* data, int alignment) //判断是否是对齐了
{
// 又是一个经典算法, 参见<Hacker's Delight>
return ((uintptr_t)data & (alignment - 1)) == 0;
}
void main() {
int totalsize = 10;
int* data = (int*)aligned_malloc(sizeof(int)*totalsize, 32);
if (isAligned(data, 32)) {
std::cout << "isAligned\n";
}
memset(data, 0, sizeof(int)*totalsize);
data[5] = 1;
for (int i = 0; i < totalsize; i++) {
std::cout << data[i] << " ";
}
std::cout << "\n";
aligned_free<int>(data);
std::cout << "aligned_free\n";
while(1){}
}