(内存池) 基于嵌入式指针的简单内存池

文章目录

前言

内存池_百度百科 (baidu.com)

(Memory Pool)是一种内存分配方式,又被称为固定大小区块规划(fixed-size-blocks allocation)。通常我们习惯直接使用new、malloc等API申请分配内存,这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。

内存池的实现方式多种多样,而本文仅实现一个简单的内存池,主要运用到嵌入式指针。

嵌入式指针,指的在数据单元中,用一部分空间保存某一块空间的地址信息。实现方式多种多样。

本示例的代码量非常少,但能够满足最基本的内存池需要的操作。

Code

MemoryPool

cpp 复制代码
#include <cassert>
#include <cstdint>

namespace lotus {
template <typename Type, size_t Element_Count>
class MemoryPool {
    using UNIT_TYPE = uint8_t;
    static constexpr size_t BLOCK_SIZE = sizeof(Type);

private:
    void* m_buffer = nullptr;
    void* m_freeHead = nullptr;

public:
    MemoryPool() {
        static_assert(sizeof(Type) >= sizeof(void*), "size is to small");
        m_buffer = malloc(BLOCK_SIZE * Element_Count);
        m_freeHead = nullptr;

        for (size_t i = 0; i < Element_Count; i += 1) {
            void* index = (UNIT_TYPE*)m_buffer + (i * BLOCK_SIZE);
            *(void**)(index) = m_freeHead;
            m_freeHead = index;
        }
    }

    ~MemoryPool() {
        if (m_buffer) {
            free(m_buffer);
            m_buffer = nullptr;
        }
        if (m_freeHead) {
            m_freeHead = nullptr;
        }
    }

public:
    void* allocate() {
        assert(m_freeHead);
        void* res = m_freeHead;
        m_freeHead = (void*)(*(void**)m_freeHead);
        return res;
    }

    void deallocate(void* p) noexcept {
        *(void**)p = m_freeHead;
        m_freeHead = p;
    }
};
}  // namespace lotus

Test

cpp 复制代码
#include <iostream>

namespace lotus {
template <typename Type, size_t Element_Count>
class MemoryPool {
    // ...
}
}

int main() {
    using Tp = long long;
    lotus::MemoryPool<Tp, 100U> pool;

    {
        Tp* p1 = (Tp*)pool.allocate();
        *p1 = 123;
        printf("p = %p\t val = %lld\n", p1, *p1);
        Tp* p2 = (Tp*)pool.allocate();
        *p2 = 123456;
        printf("p = %p\t val = %lld\n", p2, *p2);

        pool.deallocate(p1);
        printf("after free p1\n");
        Tp* p3 = (Tp*)pool.allocate();
        printf("p = %p\t val = %lld\n", p3, *p3);
    }

    {
        printf(">> show near allocate()\n");
        for (int i = 0; i < 10; i += 1) {
            Tp* p = (Tp*)pool.allocate();
            printf("p = %p\t pval = %x\n", p, *p);
        }
    }
}

简单讲解

应用方式

将数据类型和类型单元数量作为模板参数确定

每次void* allocate()获取一个单元空间。

每次void deallocate(void* p)释放一个空间。请保证传入的是void* allocate()中获取的。


简单解释一下本文使用嵌入式指针的方式:

cpp 复制代码
// p是一个指针,信息是一个地址值
void* p;
// pp = (void**)(p) 将p的信息强转为一个二级指针
// *pp 解引用,表示pp这块地址`的内容`
*(void**)(p);

参数

cpp 复制代码
template <typename Type, size_t Element_Count>
// 数据类型
Type
// 最大申请个数
Element_Count

// 保证每个内存单元式8字节
using UNIT_TYPE = uint8_t;
// type的大小作为一个数据块
static constexpr size_t BLOCK_SIZE = sizeof(Type);

// 总内存指针
void* m_buffer = nullptr;
// 头指针,时刻指向可分配出的内存地址
void* m_freeHead = nullptr;

MemoryPool();

cpp 复制代码
MemoryPool() {
    // 确保目前元素大小比指针大小大
    static_assert(sizeof(Type) >= sizeof(void*), "size is to small");
    // 一口气申请大内存
    m_buffer = malloc(BLOCK_SIZE * Element_Count);
    m_freeHead = nullptr;

    for (size_t i = 0; i < Element_Count; i += 1) {
        // 将大内存分块
        void* index = (UNIT_TYPE*)m_buffer + (i * BLOCK_SIZE);
        // !核心!嵌入式指针
        // 总体表现为每后一个空间都保存前一个空间的地址信息
        *(void**)(index) = m_freeHead;
        m_freeHead = index;
    }
}

~MemoryPool();

cpp 复制代码
~MemoryPool() {
    // 释放内存
    if (m_buffer) {
        free(m_buffer);
        m_buffer = nullptr;
    }
    // 仅起视图代理作用,不掌管内存
    if (m_freeHead) {
        m_freeHead = nullptr;
    }
}

void* allocate();

cpp 复制代码
void* allocate() {
    assert(m_freeHead);
    // 分配出去的地址
    void* res = m_freeHead;
    // 借助嵌入式指针,将head指向下一个空间
    m_freeHead = (void*)(*(void**)m_freeHead);
    return res;
}

void deallocate(void* p);

cpp 复制代码
void deallocate(void* p) noexcept {
    // 嵌入式指针赋值
    // p的空间的地址保存head的信息
    *(void**)p = m_freeHead;
    // head更新为p
    m_freeHead = p;
}

测试效果

g++ (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 7.3.0

shell 复制代码
p = 0000000000751d98     val = 123    
p = 0000000000751d90     val = 123456 
after free p1
p = 0000000000751d98     val = 7675272
>> show near allocate()
p = 0000000000751d88     pval = 751d80
p = 0000000000751d80     pval = 751d78
p = 0000000000751d78     pval = 751d70
p = 0000000000751d70     pval = 751d68
p = 0000000000751d68     pval = 751d60
p = 0000000000751d60     pval = 751d58
p = 0000000000751d58     pval = 751d50
p = 0000000000751d50     pval = 751d48
p = 0000000000751d48     pval = 751d40
p = 0000000000751d40     pval = 751d38

第一部分可以看出,将p1归还后,再申请p3,获得的还是p1的地址。

第一部分可以看出,申请出空间的内容就是下一个空间的地址,如pval = 751d78的下一次申请为p = 0000000000751d78




END

相关推荐
颇有几分姿色15 分钟前
深入理解 Linux 内存管理:free 命令详解
linux·运维·服务器
白子寰20 分钟前
【C++打怪之路Lv14】- “多态“篇
开发语言·c++
小芒果_0125 分钟前
P11229 [CSP-J 2024] 小木棍
c++·算法·信息学奥赛
gkdpjj31 分钟前
C++优选算法十 哈希表
c++·算法·散列表
王俊山IT32 分钟前
C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(5)
开发语言·c++·笔记·学习
-Even-35 分钟前
【第六章】分支语句和逻辑运算符
c++·c++ primer plus
AndyFrank1 小时前
mac crontab 不能使用问题简记
linux·运维·macos
筱源源1 小时前
Kafka-linux环境部署
linux·kafka
我是谁??1 小时前
C/C++使用AddressSanitizer检测内存错误
c语言·c++
算法与编程之美2 小时前
文件的写入与读取
linux·运维·服务器