在资源受限的环境或高性能场景中,合理管理内存至关重要。这一节我们探讨一些实用的内存优化技巧。
减少内存分配
// 不好:频繁分配 void bad() { for (int i = 0; i < 1000; ++i) { string s = "Hello"; // 每次都分配 // 处理 s } } // 好:复用对象 void good() { string s; for (int i = 0; i < 1000; ++i) { s = "Hello"; // 可能复用内存 // 处理 s } }
预分配容量
收缩容器
移动语义避免复制
使用合适的数据结构
随机访问多
vector
连续内存,缓存友好
中间插删多
list
O(1) 插删
频繁查找
unordered_set/map
O(1) 查找
有序且查找
set/map
O(log n) 查找
固定大小
array
无堆分配
结构体内存布局
位域节省空间
小字符串优化(SSO)
对象池模式
内存映射文件
处理大文件时,可以使用内存映射而不是读取到内存。
测量内存使用
优化一个结构体的内存布局。
比较 vector::reserve() 前后的性能差异。
vector::reserve()
实现一个简单的内存池。
Last updated 3 months ago
// 不好:多次扩容 vector<int> v; for (int i = 0; i < 10000; ++i) { v.push_back(i); // 可能多次重新分配 } // 好:预分配 vector<int> v; v.reserve(10000); // 一次性分配 for (int i = 0; i < 10000; ++i) { v.push_back(i); // 不会重新分配 }
vector<int> v(10000); // ... 使用后 ... v.clear(); // 清空内容,但容量不变 // 释放多余内存 v.shrink_to_fit(); // C++11 // 或者交换技巧 vector<int>().swap(v); // v 变为空且容量最小
// 不好:复制大对象 vector<string> createStrings() { vector<string> result; // ... 填充 result ... return result; // C++11 前会复制 } // 好:利用移动语义(C++11 自动优化) vector<string> createStrings() { vector<string> result; result.push_back("hello"); return result; // 移动,不复制 } // 显式移动 void process(vector<string>&& data) { vector<string> local = move(data); // 移动 }
// 不好:内存对齐导致空洞 struct Bad { char a; // 1 byte // 7 bytes padding double b; // 8 bytes char c; // 1 byte // 7 bytes padding }; // 总共 24 bytes // 好:调整成员顺序 struct Good { double b; // 8 bytes char a; // 1 byte char c; // 1 byte // 6 bytes padding }; // 总共 16 bytes int main() { cout << sizeof(Bad) << endl; // 24 cout << sizeof(Good) << endl; // 16 return 0; }
// 普通结构 struct RegularFlags { bool flag1; // 1 byte (通常按 4 对齐) bool flag2; bool flag3; }; // 可能 3-4 bytes // 位域 struct BitFlags { unsigned int flag1 : 1; unsigned int flag2 : 1; unsigned int flag3 : 1; }; // 4 bytes,但可存 32 个标志 // 更紧凑:直接用整数 class CompactFlags { uint8_t flags = 0; public: void setFlag1(bool v) { if (v) flags |= 1; else flags &= ~1; } bool getFlag1() const { return flags & 1; } // ... }; // 1 byte
// 大多数 string 实现都有 SSO // 短字符串存在栈上,不分配堆内存 string short_str = "hi"; // 通常在栈上 string long_str = "This is a very long string..."; // 在堆上 // 利用 string_view 避免复制(C++17) void process(string_view sv) { // sv 不拥有字符串,只是视图 cout << sv << endl; } process("hello"); // 不分配内存
template<typename T, size_t PoolSize = 100> class ObjectPool { private: union Slot { char data[sizeof(T)]; Slot* next; }; Slot pool[PoolSize]; Slot* freeList = nullptr; public: ObjectPool() { for (size_t i = 0; i < PoolSize - 1; ++i) { pool[i].next = &pool[i + 1]; } pool[PoolSize - 1].next = nullptr; freeList = &pool[0]; } template<typename... Args> T* create(Args&&... args) { if (!freeList) return nullptr; Slot* slot = freeList; freeList = freeList->next; return new (slot->data) T(forward<Args>(args)...); } void destroy(T* obj) { obj->~T(); Slot* slot = reinterpret_cast<Slot*>(obj); slot->next = freeList; freeList = slot; } };
// 这需要平台特定 API // Windows: CreateFileMapping, MapViewOfFile // Linux: mmap // 概念示例 class MemoryMappedFile { public: void* map(const string& filename, size_t size); void unmap(); // ... };
// 简单的内存追踪 size_t totalAllocated = 0; void* operator new(size_t size) { totalAllocated += size; return malloc(size); } void operator delete(void* p) noexcept { free(p); // 注意:无法追踪释放的大小 } int main() { vector<int> v(1000); cout << "Allocated: " << totalAllocated << " bytes" << endl; return 0; }