裸指针本身不控制内存限额,限额需由自定义operator new、内存池或raii句柄等上层机制实现;全局重载new可实现进程级硬限额,std::pmr::monotonic_buffer_resource适合请求级软限额,而memoryhandle类封装则兼顾安全与策略灵活性。

指针本身不控制内存限额,真正起作用的是分配器和资源管理策略
直接用裸指针(如 int*)无法实现“动态内存限额控制”——它只是地址容器,不带配额逻辑。限额必须由上层机制注入:比如自定义 operator new、封装的内存池类、或 RAII 容器配合全局计数器。裸指针甚至不知道自己占了多少字节,更不会拒绝超限分配。
常见错误现象:new int[1000000] 突然成功,但程序已突破预设 10MB 限额;或者手动 delete 后忘记减计数,导致后续误判超限。
- 使用场景:嵌入式设备内存受限、服务端请求级内存隔离、单元测试中模拟 OOM
- 关键点:限额判断必须发生在分配动作之前,且需线程安全(尤其在多线程
new场景下) - 性能影响:每次
new都要查原子计数器+比较,比默认分配慢 5%~15%,但远好于事后std::bad_alloc
用重载 global operator new 实现进程级硬限额
这是最直接的方式:拦截所有裸指针分配,统一检查限额。注意它不覆盖 std::vector 内部分配(除非也重载其使用的分配器),只管 new/new[]。
static std::atomic_size_t used_bytes{0};
static const size_t LIMIT = 10 * 1024 * 1024; // 10MB
<p>void* operator new(size_t n) {
size_t total = used_bytes.fetch_add(n, std::memory_order_relaxed) + n;
if (total > LIMIT) {
used_bytes.fetch_sub(n, std::memory_order_relaxed);
throw std::bad_alloc{};
}
return malloc(n);
}</p><p>void operator delete(void* p) noexcept {
// 注意:这里无法知道 p 的 size,需配套重载 operator new(size_t, std::nothrow_t)
// 或改用内存池(见下节)避免此问题
}</p>
容易踩的坑:operator delete 拿不到原始分配大小,裸指针方案下无法精准扣减;若用 malloc 分配,free 时也无法反查 size。解决方案是强制走内存池,或改用 std::pmr::polymorphic_allocator。
用 std::pmr::monotonic_buffer_resource 实现请求级软限额
比全局重载更可控:每个请求绑定独立缓冲区,超限时自动 fallback 到上游(如系统堆),不抛异常。适合 Web 请求处理这类有明确生命周期的场景。
char buffer[8_MB];
std::pmr::monotonic_buffer_resource pool{buffer, sizeof(buffer)};
std::pmr::polymorphic_allocator<int> alloc{&pool};
<p>std::vector<int std::pmr::polymorphic_allocator>> v{alloc};
v.reserve(100000); // 若超出 8MB,自动切到默认分配器,但可检测 pool.used_bytes()
if (pool.used_bytes() > 7.5 <em> 1024 </em> 1024) {
log_warning("接近内存限额");
}</int></p></int>
- 参数差异:
monotonic_buffer_resource不回收中间内存(只增不减),适合短生命周期对象;若需复用,换std::pmr::synchronized_pool_resource - 兼容性:C++17 起支持,GCC 7.1+ / Clang 6.0+ 可用,MSVC 2019 16.5+ 支持完整
- 注意:
std::pmr::vector的迭代器失效规则与标准vector一致,但容量增长策略受底层 resource 影响
裸指针 + 自定义句柄类才是实际工程中的常用组合
直接暴露 int* 给业务代码风险太高。真实项目里,限额控制藏在句柄背后:用户拿到的是 MemoryHandle,内部持有一个带配额检查的指针工厂。
class MemoryHandle {
static std::atomic_size_t global_used_;
void* ptr_ = nullptr;
size_t size_ = 0;
public:
explicit MemoryHandle(size_t n) {
if (global_used_.load() + n > LIMIT) throw std::runtime_error("quota exceeded");
ptr_ = malloc(n);
if (!ptr_) throw std::bad_alloc{};
size_ = n;
global_used_.fetch_add(n);
}
~MemoryHandle() {
if (ptr_) {
global_used_.fetch_sub(size_);
free(ptr_);
}
}
int* as_int_ptr() { return static_cast<int>(ptr_); } // 显式转换,提醒使用者责任
};
</int>
复杂点在于:不同线程的限额是否隔离?是否允许临时突破(burst)?这些策略无法靠指针语法表达,必须在句柄类的构造/析构/拷贝语义里显式编码。最容易被忽略的是——拷贝 MemoryHandle 时,是共享底层内存并共担配额,还是复制数据并双份扣减?这决定了你的 operator= 怎么写。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











