当前位置: 首页 > news >正文

C++ 内存分配器的作用

在 C++ 中,内存分配器(Allocator) 是一个用于管理内存分配和释放的机制。

📚 一、内存分配器基础

默认分配器:std::allocator<T>

C++ 标准库提供了一个默认的分配器模板类 std::allocator<T>,它封装了底层的内存分配和释放操作。默认分配器通过调用全局 operator newoperator delete 来分配和释放内存。

基本方法:
  • allocate(n):分配原始未构造的内存空间,能容纳 n 个 T 类型对象。
  • deallocate(p, n):释放由 allocate() 分配的内存,p 是指针,n 是元素数。
  • construct(p, args…):在已分配的内存 p 上构造一个对象。
  • destroy§:销毁 p 指向的对象。

✅ 二、使用默认分配器 vs. 自定义分配器

使用默认分配器

当我们创建一个标准容器(如 std::vector),如果没有指定分配器,则会使用默认分配器 std::allocator<T>。这意味着每次需要扩展容器容量时,都会调用 newdelete 来分配和释放内存。

示例代码:
#include <iostream>
#include <vector>int main() {std::vector<int> vec;for (int i = 0; i < 10; ++i) {vec.push_back(i);std::cout << "Vector size: " << vec.size() << ", capacity: " << vec.capacity() << std::endl;}return 0;
}

输出示例:

Vector size: 1, capacity: 1
Vector size: 2, capacity: 2
Vector size: 3, capacity: 4
Vector size: 4, capacity: 4
Vector size: 5, capacity: 8
Vector size: 6, capacity: 8
Vector size: 7, capacity: 8
Vector size: 8, capacity: 8
Vector size: 9, capacity: 16
Vector size: 10, capacity: 16

可以看到,随着元素数量的增加,vector 的容量会按照一定的策略增长(通常是翻倍),这会导致多次调用 newdelete,带来一定的性能开销。

自定义分配器示例:

#include <iostream>
#include <vector>
#include <memory>template<typename T>
class SimplePoolAllocator {
public:using value_type = T;SimplePoolAllocator() noexcept = default;template<typename U>SimplePoolAllocator(const SimplePoolAllocator<U>&) noexcept {}T* allocate(std::size_t n) {std::cout << "Allocating " << n << " elements of type " << typeid(T).name() << std::endl;return static_cast<T*>(::operator new(n * sizeof(T)));}void deallocate(T* p, std::size_t n) {std::cout << "Deallocating " << n << " elements" << std::endl;::operator delete(p);}
};template<typename T, typename U>
bool operator==(const SimplePoolAllocator<T>&, const SimplePoolAllocator<U>&) { return true; }template<typename T, typename U>
bool operator!=(const SimplePoolAllocator<T>&, const SimplePoolAllocator<U>&) { return false; }int main() {std::vector<int, SimplePoolAllocator<int>> vec;for (int i = 0; i < 10; ++i) {vec.push_back(i);std::cout << "Vector size: " << vec.size() << ", capacity: " << vec.capacity() << std::endl;}return 0;
}

输出示例:

Allocating 1 elements of type i
Vector size: 1, capacity: 1
Allocating 2 elements of type i
Deallocating 1 elements
Vector size: 2, capacity: 2
Allocating 4 elements of type i
Deallocating 2 elements
Vector size: 3, capacity: 4
...

在这个例子中,我们简单地记录了内存分配和释放的操作。

当然可以!在实际应用中,自定义分配器(Custom Allocator) 最常见的用途之一就是实现 内存池(Memory Pool)。内存池是一种预分配固定大小的内存块集合,避免频繁调用 newdelete,从而大幅提升性能,尤其适用于高频创建和销毁对象的场景。


三、应用场景:游戏开发中的对象池

以一个游戏引擎为例,游戏中可能会频繁创建和销毁“子弹”对象。如果每次都使用默认分配器进行动态内存分配,会导致:

  • 高频调用 malloc/freenew/delete
  • 内存碎片
  • 分配速度慢

我们可以使用一个 内存池分配器 来解决这些问题。


📦示例:带内存池的自定义分配器(用于 std::vector

我们将实现一个简单的固定大小内存池分配器(Fixed Block Memory Pool Allocator),并将其用于 std::vector

✅ 示例目标:

  • 实现一个能管理固定大小对象的内存池。
  • 将其作为 std::vector 的自定义分配器。
  • 提升频繁分配/释放小对象时的性能。

🔨 步骤 1:实现内存池类

#include <iostream>
#include <vector>
#include <memory>
#include <cassert>// 固定大小内存池
class FixedBlockAllocator {
private:struct FreeListNode {FreeListNode* next;};size_t block_size_;size_t pool_size_;char* memory_pool_;FreeListNode* free_list_;public:FixedBlockAllocator(size_t block_size, size_t num_blocks): block_size_(block_size), pool_size_(num_blocks) {assert(block_size >= sizeof(FreeListNode));memory_pool_ = reinterpret_cast<char*>(::operator new(block_size * num_blocks));free_list_ = reinterpret_cast<FreeListNode*>(memory_pool_);// 初始化空闲链表for (size_t i = 0; i < num_blocks - 1; ++i) {FreeListNode* node = reinterpret_cast<FreeListNode*>(memory_pool_ + i * block_size_);node->next = reinterpret_cast<FreeListNode*>(memory_pool_ + (i + 1) * block_size_);}FreeListNode* last_node = reinterpret_cast<FreeListNode*>(memory_pool_ + (num_blocks - 1) * block_size_);last_node->next = nullptr;}~FixedBlockAllocator() {::operator delete(memory_pool_);}void* allocate() {if (!free_list_) {return ::operator new(block_size_); // 超出池容量,退化为普通 new}void* p = free_list_;free_list_ = free_list_->next;return p;}void deallocate(void* p) {if (p >= memory_pool_ && p < memory_pool_ + block_size_ * pool_size_) {// 归还到内存池FreeListNode* node = static_cast<FreeListNode*>(p);node->next = free_list_;free_list_ = node;} else {// 不属于内存池,使用 delete::operator delete(p);}}
};

🔨 步骤 2:实现自定义分配器适配器

为了让这个内存池可以作为 STL 容器的分配器,我们需要封装成标准接口。

template <typename T, size_t BLOCK_SIZE = 1024>
class PoolAllocator {
private:FixedBlockAllocator& pool_;public:using value_type = T;PoolAllocator(FixedBlockAllocator& pool) noexcept : pool_(pool) {}template <typename U>PoolAllocator(const PoolAllocator<U, BLOCK_SIZE>& other) noexcept: pool_(other.pool_) {}T* allocate(std::size_t n) {assert(n == 1); // 只支持单个对象分配return static_cast<T*>(pool_.allocate());}void deallocate(T* p, std::size_t n) {assert(n == 1);pool_.deallocate(p);}
};template <typename T, typename U, size_t BS>
bool operator==(const PoolAllocator<T, BS>& a, const PoolAllocator<U, BS>& b) {return &a.pool_ == &b.pool_;
}template <typename T, typename U, size_t BS>
bool operator!=(const PoolAllocator<T, BS>& a, const PoolAllocator<U, BS>& b) {return !(a == b);
}

🔨 步骤 3:使用内存池分配器的 std::vector

我们来测试一下这个分配器在 std::vector 中的表现。

struct Bullet {float x, y, angle;int damage;
};int main() {const size_t POOL_SIZE = 1000;FixedBlockAllocator pool(sizeof(Bullet), POOL_SIZE);using BulletVector = std::vector<Bullet, PoolAllocator<Bullet>>;BulletVector bullets(PoolAllocator<Bullet>(pool));// 插入大量子弹对象for (int i = 0; i < 2000; ++i) {bullets.push_back({1.0f, 2.0f, 0.5f, 10});}std::cout << "Total elements: " << bullets.size() << std::endl;return 0;
}

📈 三、性能优势分析

指标默认分配器 (std::allocator)自定义内存池分配器
内存分配速度慢(每次调用 new快(从链表取)
内存释放速度慢(每次调用 delete极快(归还链表)
内存碎片几乎无
扩展性不可控可定制(如缓存、日志等)

在本例中,前 1000 次分配使用内存池,后 1000 次自动回退到 new,你可以根据需要扩展内存池大小或实现多级内存池。

🛠️ 四、示例进阶

要支持 不同大小对象的分配,我们可以构建一个更通用的内存池系统,即所谓的 Slab Allocator(分块分配器)多级内存池(Multi-block Memory Pool)

这种分配器可以管理多个固定大小的内存池,每个池专门处理某一类大小的对象。当请求分配时,根据对象大小选择合适的池进行分配,从而避免频繁调用 new/delete,减少内存碎片并提升性能。


🧱 一、设计目标
  • 支持任意大小对象的分配。
  • 内部维护多个固定大小的内存池(例如:4B、8B、16B、32B…)。
  • 对于大于某个阈值的对象(如 1024B),直接使用默认分配器。
  • 提供标准接口用于 STL 容器或自定义类型。

🔨 二、实现步骤

我们将构建以下组件:

  1. MemoryPoolManager:主控类,管理多个固定大小的内存池。
  2. FixedSizePool:单个固定大小的内存池(类似前面的 FixedBlockAllocator)。
  3. GeneralAllocator:对外的标准分配器接口,适配 STL 容器。

✅ 示例代码:支持多种大小对象分配的通用分配器
步骤 1:固定大小内存池类
// FixedSizePool.h
#pragma once
#include <cstddef>
#include <vector>
#include <cassert>class FixedSizePool {
private:size_t block_size_;size_t num_blocks_;void* memory_pool_;void** free_list_;public:FixedSizePool(size_t block_size, size_t num_blocks);~FixedSizePool();void* allocate();void deallocate(void* p);size_t block_size() const { return block_size_; }
};
// FixedSizePool.cpp
#include "FixedSizePool.h"
#include <iostream>
#include <cstdlib>FixedSizePool::FixedSizePool(size_t block_size, size_t num_blocks): block_size_(block_size), num_blocks_(num_blocks) {assert(block_size >= sizeof(void*)); // 至少能放一个指针memory_pool_ = std::malloc(block_size * num_blocks_);if (!memory_pool_) throw std::bad_alloc();free_list_ = reinterpret_cast<void**>(memory_pool_);char* pool_bytes = reinterpret_cast<char*>(memory_pool_);for (size_t i = 0; i < num_blocks_ - 1; ++i) {free_list_[i] = pool_bytes + block_size_ * (i + 1);}free_list_[num_blocks_ - 1] = nullptr;
}FixedSizePool::~FixedSizePool() {std::free(memory_pool_);
}void* FixedSizePool::allocate() {if (!free_list_) return nullptr;void* p = free_list_;free_list_ = reinterpret_cast<void**>(*free_list_);return p;
}void FixedSizePool::deallocate(void* p) {if (!p) return;// 检查是否在内存池范围内char* start = reinterpret_cast<char*>(memory_pool_);char* end = start + block_size_ * num_blocks_;if (p >= start && p < end) {*reinterpret_cast<void**>(p) = free_list_;free_list_ = reinterpret_cast<void**>(p);}
}

步骤 2:多级内存池管理器
// MemoryPoolManager.h
#pragma once
#include "FixedSizePool.h"
#include <unordered_map>
#include <vector>
#include <mutex>class MemoryPoolManager {
private:std::vector<FixedSizePool> pools_;std::unordered_map<size_t, FixedSizePool*> size_to_pool_;std::mutex mtx_;public:static MemoryPoolManager& instance() {static MemoryPoolManager manager;return manager;}void initialize();void* allocate(size_t size);void deallocate(void* ptr, size_t size);
};
// MemoryPoolManager.cpp
#include "MemoryPoolManager.h"
#include <iostream>void MemoryPoolManager::initialize() {static const size_t pool_sizes[] = { 4, 8, 16, 32, 64, 128, 256, 512 };for (auto sz : pool_sizes) {pools_.emplace_back(FixedSizePool(sz, 1000));}for (auto& pool : pools_) {size_to_pool_[pool.block_size()] = &pool;}
}void* MemoryPoolManager::allocate(size_t size) {std::lock_guard<std::mutex> lock(mtx_);// 找到合适大小的池(向上对齐)for (const auto& kv : size_to_pool_) {if (kv.first >= size) {void* p = kv.second->allocate();if (p) return p;}}// 超出预设池范围,使用默认 newreturn ::operator new(size);
}void MemoryPoolManager::deallocate(void* ptr, size_t size) {std::lock_guard<std::mutex> lock(mtx_);for (const auto& kv : size_to_pool_) {if (kv.first >= size) {kv.second->deallocate(ptr);return;}}// 非池内内存,使用 delete::operator delete(ptr);
}

步骤 3:自定义通用分配器(适配 STL)
// GeneralAllocator.h
#pragma once
#include <memory>
#include "MemoryPoolManager.h"template<typename T>
class GeneralAllocator {
public:using value_type = T;GeneralAllocator() noexcept = default;template<typename U>GeneralAllocator(const GeneralAllocator<U>&) noexcept {}T* allocate(std::size_t n) {if (n != 1) throw std::bad_alloc(); // 本例只支持单对象分配return static_cast<T*>(MemoryPoolManager::instance().allocate(sizeof(T)));}void deallocate(T* p, std::size_t n) {if (n != 1) return;MemoryPoolManager::instance().deallocate(p, sizeof(T));}
};template<typename T, typename U>
bool operator==(const GeneralAllocator<T>&, const GeneralAllocator<U>&) {return true;
}template<typename T, typename U>
bool operator!=(const GeneralAllocator<T>& a, const GeneralAllocator<U>& b) {return false;
}

✅ 四、使用示例:为多种对象提供内存池服务
#include <vector>
#include <list>
#include <string>
#include "GeneralAllocator.h"int main() {// 初始化内存池MemoryPoolManager::instance().initialize();// 使用通用分配器创建容器using MyAlloc = GeneralAllocator<int>;std::vector<int, MyAlloc> vec;for (int i = 0; i < 1000; ++i) {vec.push_back(i);}// 不同大小对象也能共享内存池std::list<std::string, GeneralAllocator<std::string>> str_list;for (int i = 0; i < 100; ++i) {str_list.emplace_back("Hello");}return 0;
}

📈 五、优势总结
特性描述
支持任意大小对象根据大小选择最适合的内存池
减少内存碎片同一类大小对象使用相同池
提高性能避免频繁调用 new/delete
可扩展性强可以添加线程安全、日志等功能
兼容 STL作为分配器可插入任意容器

🧱 四、自定义分配器的优势

性能优化

通过自定义分配器,特别是实现内存池技术,可以显著减少内存碎片化并加快分配速度。例如,在游戏开发或高性能计算领域,使用定制化的内存池可以避免频繁的 new/delete 操作带来的性能损失。

资源隔离

不同的模块可以使用独立的分配器,从而避免不同模块间的内存污染问题。这对于大型项目尤其重要,可以帮助定位内存泄漏等问题。

日志和调试

可以在自定义分配器中添加日志记录功能,方便追踪内存分配和释放的情况,有助于检测潜在的内存泄漏或性能瓶颈。


🧪 五、总结

方案优点缺点
使用默认分配器简单易用,适用于大多数场景可能在高并发或高性能要求下表现不佳
使用自定义分配器可根据需求优化内存分配策略,提升性能实现复杂,容易出错

对于大多数应用程序来说,默认分配器已经足够好。然而,在特定场景下(如高性能要求、资源受限环境),自定义分配器可以带来显著的好处。


📘 六、参考资料

  • C++ Reference - std::allocator
http://www.lryc.cn/news/573254.html

相关文章:

  • AI+预测3D新模型百十个定位预测+胆码预测+去和尾2025年6月21日第115弹
  • 【舞蹈】编排:如何对齐拍子并让小节倍数随BPM递减
  • 56-Oracle SQL Tuning Advisor(STA)
  • hot100——第六周
  • MagnTek MT6816-ACD 一款基于各向异性磁阻(AMR)技术的磁性角度传感器 IC
  • wordpress外贸独立站常用留言表单插件 contact form 7
  • 探索 Oracle Database 23ai 中的 SQL 功能
  • 小程序右上角○关闭事件
  • 基于深度学习的侧信道分析(DLSCA)Python实现(带测试)
  • RNN工作原理和架构
  • `teleport` 传送 API 的使用:在 Vue 3 中的最佳实践
  • Thrift 服务端的完整示例
  • 【设计模式】4.代理模式
  • 分组交换比报文交换的传输时延更低
  • PHP语法基础篇(五):流程控制
  • Occt几何内核快速入门
  • 力扣网C语言编程题:多数元素
  • OPENPPP2传输层控制算法剖析及漏洞修复对抗建议
  • 5.3 VSCode使用FFmpeg库
  • Git 使用手册:从入门到精通
  • 基于Qt的UDP主从服务器设计与实现
  • 【Linux第四章】gcc、makefile、git、GDB
  • 从需求到落地:充电桩APP开发的定制化流程与核心优势
  • 免费1000套编程教学视频资料视频(涉及Java、python、C C++、R语言、PHP C# HTML GO)
  • Python subprocess 模块详解
  • 60-Oracle 10046事件-实操
  • Java面试复习指南:JVM原理、并发编程与Spring框架
  • 微服务架构的适用
  • Zephyr 电源管理机制深度解析:从 Tickless Idle 到平台 Suspend 实践
  • 【设计模式】6.原型模式