C++ 内存池(Memory Pool)详解

1. 基本概念

内存池是一种内存管理技术,旨在提高内存分配的效率。它通过预先分配一块大的内存区域(池),然后从中分配小块内存来满足应用程序的需求。这样可以减少频繁的内存分配和释放带来的性能开销。

2. 设计思路

内存池的设计通常遵循以下步骤:

  • 预分配内存:在程序开始时,预先分配一块较大的内存区域。
  • 管理空闲块:使用链表、栈或数组等数据结构管理可用内存块。
  • 分配和释放:提供分配和释放接口,让用户从内存池中获取和释放内存。
  • 回收机制:当内存块被释放时,将其返回到内存池中,便于后续使用。

3. 原理

内存池的核心原理是降低内存分配的时间复杂度。标准的 newdelete 操作在需要内存时会与操作系统频繁交互,可能会造成较大的开销。而内存池将这种频繁操作集中到池的初始化阶段,后续的分配和释放则在池内进行,速度更快。

4. 使用场景

内存池适用于以下场景:

  • 游戏开发:频繁创建和销毁对象,例如子弹、敌人等。
  • 高性能计算:实时系统对内存分配速度的高要求。
  • 网络编程:处理大量小数据包时,内存池可以提高性能。
  • 嵌入式系统:资源有限的环境中,避免频繁的动态内存分配。

5. 详细讲解

  • 内存池的优势

    • 性能提升:通过减少系统调用,提高内存分配和释放的速度。
    • 内存碎片减少:通过统一管理,减少内存碎片的问题。
    • 简化内存管理:可以设计为自动回收机制,降低内存泄漏的风险。
  • 内存池的劣势

    • 内存浪费:如果分配的块未被充分利用,可能会造成内存浪费。
    • 复杂性增加:需要额外的代码管理内存池,增加了系统的复杂性。
  • 扩展功能

    • 多线程支持:在多线程环境中,可以使用锁或无锁队列管理内存池。
    • 调试功能:可以在分配和释放时记录堆栈信息,便于调试内存泄漏。

6. 场景示例

内存池的详细实现

1. 内存池类的结构

内存池主要由以下几个部分构成:

  • 内存块:固定大小的内存单元。
  • 内存池管理:负责分配和释放内存块。
  • 空闲块管理:使用链表或栈来管理未使用的内存块。
2. 经典的内存池实现

#include <iostream>
#include <vector>
#include <stdexcept>
#include <cassert>

class MemoryPool {
public:
    // 构造函数,初始化内存池
    MemoryPool(size_t blockSize, size_t blockCount)
        : m_blockSize(blockSize), m_blockCount(blockCount), m_usedBlocks(0) {
        // 分配内存池
        m_pool = malloc(blockSize * blockCount);
        if (!m_pool) {
            throw std::bad_alloc(); // 内存分配失败,抛出异常
        }
        // 初始化空闲块列表
        for (size_t i = 0; i < blockCount; ++i) {
            m_freeBlocks.push_back(static_cast<char*>(m_pool) + i * blockSize);
        }
    }

    // 析构函数,释放内存池
    ~MemoryPool() {
        free(m_pool);
    }

    // 分配内存块
    void* allocate() {
        // 如果没有可用的块,返回nullptr
        if (m_freeBlocks.empty()) {
            return nullptr; // 可以改进为扩展内存池
        }
        // 从空闲块列表中取出一个块
        void* block = m_freeBlocks.back();
        m_freeBlocks.pop_back(); // 从空闲列表中移除
        m_usedBlocks++; // 增加使用计数
        return block; // 返回分配的块
    }

    // 释放内存块
    void deallocate(void* block) {
        assert(block != nullptr); // 确保要释放的块不是nullptr
        m_freeBlocks.push_back(static_cast<char*>(block)); // 添加到空闲块列表
        m_usedBlocks--; // 减少使用计数
    }

    // 获取当前使用的块数
    size_t usedBlocks() const {
        return m_usedBlocks;
    }

    // 获取空闲块的数量
    size_t freeBlocks() const {
        return m_freeBlocks.size();
    }

private:
    size_t m_blockSize;            // 每个内存块的大小
    size_t m_blockCount;           // 内存池中的块数量
    void* m_pool;                  // 内存池的起始地址
    std::vector<void*> m_freeBlocks; // 存储空闲块的列表
    size_t m_usedBlocks;           // 当前使用的块数
};

// 示例使用
int main() {
    const size_t BLOCK_SIZE = 32; // 每个块32字节
    const size_t BLOCK_COUNT = 10; // 总共10个块

    MemoryPool pool(BLOCK_SIZE, BLOCK_COUNT); // 创建内存池

    // 分配内存块
    void* block1 = pool.allocate();
    void* block2 = pool.allocate();

    std::cout << "Allocated blocks: " << block1 << ", " << block2 << std::endl;
    std::cout << "Used blocks: " << pool.usedBlocks() << std::endl;
    std::cout << "Free blocks: " << pool.freeBlocks() << std::endl;

    // 释放内存块
    pool.deallocate(block1);
    pool.deallocate(block2);

    std::cout << "After deallocation:" << std::endl;
    std::cout << "Used blocks: " << pool.usedBlocks() << std::endl;
    std::cout << "Free blocks: " << pool.freeBlocks() << std::endl;

    return 0;
}

3. 详细讲解
3.1 内存池的工作原理
  • 初始化:在创建内存池时,预先分配一大块内存,分成多个固定大小的内存块。
  • 分配:当请求内存时,从空闲块列表中取出一个块并返回。如果没有空闲块,可以考虑扩展内存池。
  • 释放:释放时将内存块返回到空闲块列表中,便于后续使用。
3.2 主要功能说明
  • allocate():从空闲块中分配一个块,返回其地址;如果没有可用块,返回 nullptr
  • deallocate():将已使用的内存块返回到空闲块列表中。
  • usedBlocks()freeBlocks():分别返回当前使用的块数和空闲块数,便于监控内存使用情况。
4. 扩展使用示例
4.1 用于游戏对象管理

假设我们有一个游戏中的子弹对象,我们可以使用内存池来管理这些对象的创建和销毁。

#include <iostream>
#include <vector>
#include <stdexcept>
#include <cassert>

class MemoryPool {
public:
    // 构造函数,初始化内存池
    MemoryPool(size_t blockSize, size_t blockCount)
        : m_blockSize(blockSize), m_blockCount(blockCount), m_usedBlocks(0) {
        // 分配内存池
        m_pool = malloc(blockSize * blockCount);
        if (!m_pool) {
            throw std::bad_alloc(); // 内存分配失败,抛出异常
        }
        // 初始化空闲块列表
        for (size_t i = 0; i < blockCount; ++i) {
            m_freeBlocks.push_back(static_cast<char*>(m_pool) + i * blockSize);
        }
    }

    // 析构函数,释放内存池
    ~MemoryPool() {
        free(m_pool);
    }

    // 分配内存块
    void* allocate() {
        // 如果没有可用的块,返回nullptr
        if (m_freeBlocks.empty()) {
            return nullptr; // 可以改进为扩展内存池
        }
        // 从空闲块列表中取出一个块
        void* block = m_freeBlocks.back();
        m_freeBlocks.pop_back(); // 从空闲列表中移除
        m_usedBlocks++; // 增加使用计数
        return block; // 返回分配的块
    }

    // 释放内存块
    void deallocate(void* block) {
        assert(block != nullptr); // 确保要释放的块不是nullptr
        m_freeBlocks.push_back(static_cast<char*>(block)); // 添加到空闲块列表
        m_usedBlocks--; // 减少使用计数
    }

    // 获取当前使用的块数
    size_t usedBlocks() const {
        return m_usedBlocks;
    }

    // 获取空闲块的数量
    size_t freeBlocks() const {
        return m_freeBlocks.size();
    }

private:
    size_t m_blockSize;            // 每个内存块的大小
    size_t m_blockCount;           // 内存池中的块数量
    void* m_pool;                  // 内存池的起始地址
    std::vector<void*> m_freeBlocks; // 存储空闲块的列表
    size_t m_usedBlocks;           // 当前使用的块数
};


class Bullet {
public:
    Bullet(int x, int y) : m_x(x), m_y(y) {
        std::cout << "Bullet created at (" << x << ", " << y << ")\n";
    }
    ~Bullet() {
        std::cout << "Bullet destroyed\n";
    }
    // 其他Bullet方法...

private:
    int m_x, m_y; // 子弹位置
};

class BulletPool {
public:
    BulletPool(size_t size) : m_pool(sizeof(Bullet), size) {}

    Bullet* acquire(int x, int y) {
        void* mem = m_pool.allocate();
        if (!mem) return nullptr; // 如果没有可用的子弹,返回nullptr
        return new (mem) Bullet(x, y); // 使用placement new创建Bullet
    }

    void release(Bullet* bullet) {
        bullet->~Bullet(); // 显式调用析构函数
        m_pool.deallocate(bullet); // 将内存块返回到池中
    }

private:
    MemoryPool m_pool; // 内存池实例
};

// 示例使用
int main() {
    BulletPool bulletPool(5); // 创建一个可容纳5个子弹的池

    Bullet* bullet1 = bulletPool.acquire(10, 20);
    Bullet* bullet2 = bulletPool.acquire(15, 25);

    bulletPool.release(bullet1); // 释放子弹
    bulletPool.release(bullet2); // 释放子弹

    return 0;
}

4.2 高性能数据处理

在需要处理大量小数据结构时,内存池可以显著提高性能:

#include <iostream>
#include <vector>
#include <stdexcept>
#include <cassert>

class MemoryPool {
public:
    // 构造函数,初始化内存池
    MemoryPool(size_t blockSize, size_t blockCount)
        : m_blockSize(blockSize), m_blockCount(blockCount), m_usedBlocks(0) {
        // 分配内存池
        m_pool = malloc(blockSize * blockCount);
        if (!m_pool) {
            throw std::bad_alloc(); // 内存分配失败,抛出异常
        }
        // 初始化空闲块列表
        for (size_t i = 0; i < blockCount; ++i) {
            m_freeBlocks.push_back(static_cast<char*>(m_pool) + i * blockSize);
        }
    }

    // 析构函数,释放内存池
    ~MemoryPool() {
        free(m_pool);
    }

    // 分配内存块
    void* allocate() {
        // 如果没有可用的块,返回nullptr
        if (m_freeBlocks.empty()) {
            return nullptr; // 可以改进为扩展内存池
        }
        // 从空闲块列表中取出一个块
        void* block = m_freeBlocks.back();
        m_freeBlocks.pop_back(); // 从空闲列表中移除
        m_usedBlocks++; // 增加使用计数
        return block; // 返回分配的块
    }

    // 释放内存块
    void deallocate(void* block) {
        assert(block != nullptr); // 确保要释放的块不是nullptr
        m_freeBlocks.push_back(static_cast<char*>(block)); // 添加到空闲块列表
        m_usedBlocks--; // 减少使用计数
    }

    // 获取当前使用的块数
    size_t usedBlocks() const {
        return m_usedBlocks;
    }

    // 获取空闲块的数量
    size_t freeBlocks() const {
        return m_freeBlocks.size();
    }

private:
    size_t m_blockSize;            // 每个内存块的大小
    size_t m_blockCount;           // 内存池中的块数量
    void* m_pool;                  // 内存池的起始地址
    std::vector<void*> m_freeBlocks; // 存储空闲块的列表
    size_t m_usedBlocks;           // 当前使用的块数
};


class Data {
public:
    Data(int value) : m_value(value) {}
    ~Data() {}
    // 数据处理方法...

private:
    int m_value; // 数据值
};

class DataPool {
public:
    DataPool(size_t size) : m_pool(sizeof(Data), size) {}

    Data* create(int value) {
        void* mem = m_pool.allocate();
        return new (mem) Data(value); // 使用placement new创建数据对象
    }

    void destroy(Data* data) {
        data->~Data(); // 显式调用析构函数
        m_pool.deallocate(data); // 将内存块返回到池中
    }

private:
    MemoryPool m_pool; // 内存池实例
};

// 示例使用
int main() {
    DataPool dataPool(100); // 创建一个可容纳100个Data对象的池

    Data* data1 = dataPool.create(42);
    Data* data2 = dataPool.create(99);

    dataPool.destroy(data1); // 释放数据
    dataPool.destroy(data2); // 释放数据

    return 0;
}

7. 总结

内存池是一种有效的内存管理技术,通过预分配和集中管理内存块,提高了内存分配和释放的效率。尽管它增加了一定的复杂性,但在高性能和实时系统中,它的优势往往是不可忽视的。理解内存池的基本概念、设计思路和使用场景,有助于在适当的地方应用这一技术。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/887223.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

pytest(三)——参数化@pytest.mark.parametrize

目录 前言 参数化场景 实际Web UI自动化中的开发场景&#xff0c;比如是一个登录框 parametrize单参数 “笛卡尔积”&#xff0c;多个参数化装饰器 重点知识 参考文献 前言 pytest.mark.parametrize 允许在测试函数或类中定义多组参数和fixtures pytest_generate_tests 允…

对于基础汇编的趣味认识

汇编语言 机器指令 机器语言是机器指令的集合 机器指令展开来讲就是一台机器可以正确执行的命令 电子计算机的机器指令是一列二进制数字 &#xff08;计算机将其转变为一列高低电平&#xff0c;使得计算机的电子器件受到驱动&#xff0c;进行运算 寄存器&#xff1a;微处理器…

C(九)while循环 --- 军训匕首操情景

匕首操&#xff0c;oi~oi~oi~~~~~ 接下来的几篇推文&#xff0c;杰哥记录的是三大循环结构的运行流程及其变式。 本篇的主角是while循环。&#x1f449; 目录&#xff1a; while循环 的组成、运行流程及其变式关键字break 和 continue 在while 循环中的作用while 循环的嵌套题目…

C/C++逆向:数据类型识别

在逆向工程中&#xff0c;数据类型识别是理解程序逻辑的重要步骤&#xff0c;因为它直接影响对程序逻辑和功能的理解&#xff0c;识别出数据类型有助于确定变量的含义和函数的行为。在分析恶意软件或者寻找安全漏洞时&#xff0c;识别数据类型能够帮助发现代码中的潜在问题。例…

【越学学糊涂的Linux系统】(5)shell命令以及运行原理|权限问题

Ⅰ.shell命名以及运行原理&#xff1a; 0x00引用&#xff1a; 什么是shell命令&#xff1f;&#xff1f; ✔️ Shell 是一种命令行解释器&#xff08;Command - Line Interpreter&#xff09;&#xff0c;它为用户提供了与操作系统内核进行交互的接口。用户通过在 She…

【Qt】控件概述(3)—— 显示类控件

显示类控件 1. QLabel——标签1.1 setPixmap设置图片1.2 setAlignment设置文本对齐方式1.3 setWordWrap设置自动换行1.4 setIndent设置缩进1.5 setMargin设置边距1.6 body 2. QLCDNumber2.1 使用QTimer实现一个倒计时效果2.2 使用循环的方式实现倒计时 3. QProgressBar——进度…

【工程测试技术】第6章 信号处理初步,频谱分析,相关系数

目录 6.1 数字信号处理的基本步骤 6.2 离散信号及其频谱分析 6.2.1 概述 6.2.2 时域采样、混叠和采样定理 6.2.3 量化和量化误差 6.2.4 截断、泄漏和窗函数 6.2.5 频域采样、时域周期延拓和栅栏效应 6.2.6 频率分辨率、整周期截断 6.3 相关分析及其应用 6.3.1 两…

【C++】--类与对象(1)

&#x1f9c7;个人主页: 起名字真南 &#x1f32d;个人专栏:【数据结构初阶】 【C语言】 【C】 目录 1 类的定义1.1 类定义格式1.1.1 Stack类1.1.2 Date类1.1.3 Struct格式 1.2 访问限定符1.3 类域 2 实例化2.2 对象大小 3 this指针 1 类的定义 1.1 类定义格式 1 class为定义…

解决磁盘负载不均——ElasticSearch 分片分配和路由设置

ES 分片分配&#xff08;Shard Allocation&#xff09;时间点&#xff1a; 初始恢复&#xff08;Initial Recovery&#xff09;副本分配&#xff08;Replica Allocation&#xff09;重平衡&#xff08;Rebalance&#xff09;节点添加或移除 小结&#xff1a; 准备移除节点时&a…

【Golang】关于Go语言字符串转换strconv

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

k8s-集群部署1

k8s-集群部署1 一、基础环境准备二、docker环境准备三、k8s集群部署1.kubeadm创建集群2.使用kubeadm引导集群 总结 一、基础环境准备 首先&#xff0c;需要准备三个服务器实例&#xff0c;这里我使用了阿里云创建了三个实例&#xff0c;如果不想花钱&#xff0c;也可以在VM上创…

1panel申请https/ssl证书自动续期

参考教程 https://hin.cool/posts/sslfor1panel.html #Acme 账户 #1panel.腾讯云dns账号 这里有一步不需要参考,腾讯云dns账号,就是子帐号授权 直接控制台搜索 访问管理 创建用户 授权搜索dns,选择第一个 点击用户名,去掉AdministratorAccess权限 5.点击api密钥生成即可…

CSS3练习--电商web

免责声明&#xff1a;本文仅做分享&#xff01; 目录 小练--小兔鲜儿 目录构建 SEO 三大标签 Favicon 图标 布局网页 版心 快捷导航&#xff08;shortcut&#xff09; 头部&#xff08;header&#xff09; logo 导航 搜索 购物车 底部&#xff08;footer&#xff0…

初学51单片机之I2C总线与E2PROM二

总结下上篇博文的结论&#xff1a; 1&#xff1a;ACK信号在SCL为高电平期间会一直保持。 2&#xff1a;在字节数据传输过程中如果发送电平跳变&#xff0c;那么电平信号就会变成重复起始或者结束的信号。&#xff08;上篇博文的测试方法还是不能够明确证明这个结论&#xff0…

字符串和字符数组(2)

6.求字符串长度 C语言中有一个库函数叫strlen&#xff0c;这个函数是专门用来求字符串长度的。strlen的使用需要包含一个头文件string.h。 strlen函数统计的是字符串中\0之前的字符个数&#xff0c;所以传递给strlen函数的字符串中必须得包含\0. 请看代码&#xff1a; #inc…

数据结构 ——— 单链表oj题:链表分割(带哨兵位单向不循环链表实现)

目录 题目要求 手搓简易单链表 代码实现 题目要求 现有一链表的头指针 ListNode* head &#xff0c;给一定值 x &#xff0c;编写一段代码将所有小于 x 的节点排在其余节点之前&#xff0c;且不能改变原来的数据顺序&#xff0c;返回重新排列后的链表的头节点 举例说明&a…

免费送源码:Javaspringboot++MySQL springboot 社区互助服务管理系统小程序 计算机毕业设计原创定制

摘 要 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受居民的喜爱&#xff0c;社区互助服务管理系统小程序被居民普遍使用&#xff0c;为…

macOS编译和运行prometheus2.54

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码)&#xff1a;https://github.com/zq2599/blog_demos 本篇概览 本文详述了在macOS(M2芯片)上编译和运行prometheus2.54版本的过程&#xff0c;以及安装node_exporter和grafana并使用prometheus指标进行展示 本地…

Redis:zset类型

Redis&#xff1a;zset类型 zset命令ZADDZCARDZCOUNTZRANGEZREVRANGEZRANGEBYSCOREZREVRANGEBYSCOREZPOPMAXBZPOPMAXZPOPMINBZPOPMINZRANKZREVRANKZSCOREZREMZREMRANGEBYRANKZREMRANGEBYSCOREZINCRBY 集合间操作ZINRERSTOREZUNIONSTORE 内部编码ziplistskiplist 在Redis中&…

单片机的两种看门狗原理解析——IWDG和WWDG

一、IWDG独立开门狗的主要性能 计时机制&#xff1a; 递减计数器 独立开门狗的初始频率&#xff1a; LSI低速内部时钟&#xff1a;RC震荡器&#xff0c;40kHz 独立开门狗是以LSI为初始频率的&#xff0c;所以独立开门狗的初始时钟频率取决与单片机本身&#xff0c;因此在使…