aligned_malloc内存对齐

发布时间 2023-07-06 15:15:14作者: 一颗红豆

来自 https://blog.csdn.net/jin739738709/article/details/122992753

什么是内存对齐

什么是[内存对齐]?有两种解释:

  1. 存放数据的首地址是某个数(通常它为4,8或者32)的倍数。
  2. 数据结构所占字节数是某个数(通常它为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){}
}