new
运算符底层实现
- 基本原理:在 C++ 中,
new
运算符首先调用 operator new
函数(全局或类特定的),这个函数负责在堆上分配内存。operator new
通常调用 malloc
函数(C 标准库函数)来从操作系统获取内存。在底层,malloc
一般通过系统调用(如 Linux 下的 brk
或 sbrk
用于扩展堆空间,或者 mmap
用于映射内存区域)从操作系统申请内存块。
- 内存分配过程:
new
表达式首先计算对象所需的内存大小,包括对象本身的大小以及可能的对齐填充。
- 然后调用
operator new
函数,该函数尝试从堆中分配足够的内存。如果分配成功,operator new
返回指向分配内存起始地址的指针。
- 接着,调用对象的构造函数,在分配的内存上初始化对象。
涉及的系统调用和内存管理机制
- 系统调用:
brk
和 sbrk
:在 Linux 系统中,brk
用于设置数据段(堆)的结束地址,sbrk
用于增加或减少数据段的大小。malloc
可以使用 sbrk
来扩展堆空间以满足内存分配请求。例如,当 malloc
需要分配较大内存块时,它可能调用 sbrk
来增加堆的大小。
mmap
:mmap
用于将文件或设备映射到内存空间,也可以用于分配匿名内存(即不与文件关联的内存)。malloc
在某些情况下(如分配较大内存块时)可能会使用 mmap
来获取内存。这样做的好处是可以利用操作系统的虚拟内存管理机制,并且在内存释放时可以直接解除映射,而不需要合并内存块。
- 内存管理机制:
- 堆管理:操作系统维护一个堆数据结构,用于跟踪已分配和未分配的内存块。常见的堆管理算法有
first - fit
(首次适应,找到第一个足够大的空闲块)、best - fit
(最佳适应,找到最小的足够大的空闲块)和 worst - fit
(最坏适应,找到最大的空闲块)。malloc
通常使用 first - fit
或 best - fit
算法来分配内存。
- 内存对齐:为了提高内存访问效率,内存分配通常需要满足特定的对齐要求。例如,某些硬件平台要求特定类型的数据(如
double
)在内存中按 8 字节对齐。malloc
和 operator new
会在分配内存时确保内存块的起始地址满足相应的对齐要求,可能会在实际分配的内存大小上增加一些填充字节。
优化策略以减少内存碎片并提高性能
- 对象池(Object Pool):
- 原理:预先分配一组对象,当需要新对象时从对象池中获取,不再使用时放回对象池,而不是频繁地调用
new
和 delete
。这样可以减少内存碎片,因为对象池中的对象大小相同,不会产生大小不一的空闲块。
- 代码示例:
#include <iostream>
#include <vector>
#include <memory>
class Object {
public:
Object() { std::cout << "Object constructed" << std::endl; }
~Object() { std::cout << "Object destructed" << std::endl; }
};
class ObjectPool {
public:
ObjectPool(size_t size) {
for (size_t i = 0; i < size; ++i) {
objects.emplace_back(std::make_unique<Object>());
}
}
std::unique_ptr<Object> getObject() {
if (objects.empty()) {
return std::make_unique<Object>();
}
std::unique_ptr<Object> obj = std::move(objects.back());
objects.pop_back();
return obj;
}
void returnObject(std::unique_ptr<Object> obj) {
objects.emplace_back(std::move(obj));
}
private:
std::vector<std::unique_ptr<Object>> objects;
};
int main() {
ObjectPool pool(10);
auto obj1 = pool.getObject();
auto obj2 = pool.getObject();
pool.returnObject(std::move(obj1));
return 0;
}
- 内存池(Memory Pool):
- 原理:类似于对象池,但更通用,用于分配任意大小的内存块。内存池预先分配一块较大的内存,然后将其分割成多个较小的内存块供程序使用。当程序释放内存块时,将其返回内存池,而不是归还给操作系统。
- 代码示例:
#include <iostream>
#include <vector>
#include <cstdlib>
class MemoryPool {
public:
MemoryPool(size_t poolSize, size_t blockSize)
: poolSize(poolSize), blockSize(blockSize) {
pool = std::malloc(poolSize);
if (!pool) {
throw std::bad_alloc();
}
for (char* p = static_cast<char*>(pool); p < static_cast<char*>(pool) + poolSize; p += blockSize) {
freeBlocks.push_back(p);
}
}
~MemoryPool() {
std::free(pool);
}
void* allocate() {
if (freeBlocks.empty()) {
return std::malloc(blockSize);
}
void* block = freeBlocks.back();
freeBlocks.pop_back();
return block;
}
void deallocate(void* block) {
if (reinterpret_cast<char*>(block) >= static_cast<char*>(pool) &&
reinterpret_cast<char*>(block) < static_cast<char*>(pool) + poolSize) {
freeBlocks.push_back(reinterpret_cast<char*>(block));
} else {
std::free(block);
}
}
private:
size_t poolSize;
size_t blockSize;
void* pool;
std::vector<char*> freeBlocks;
};
int main() {
MemoryPool pool(1024, 64);
void* block1 = pool.allocate();
void* block2 = pool.allocate();
pool.deallocate(block1);
return 0;
}
- 使用智能指针:
- 原理:智能指针(如
std::unique_ptr
、std::shared_ptr
)可以自动管理动态分配的内存,确保在对象不再使用时及时释放内存,避免内存泄漏。这有助于提高程序的整体稳定性和可维护性,间接提高性能。
- 代码示例:
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructed" << std::endl; }
~MyClass() { std::cout << "MyClass destructed" << std::endl; }
};
int main() {
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>();
std::shared_ptr<MyClass> ptr2 = std::make_shared<MyClass>();
return 0;
}
- 减少不必要的动态分配:
- 原理:尽量避免在循环或频繁调用的函数中进行动态内存分配。如果可能,预先分配足够的内存,然后在需要时使用已分配的内存。
- 代码示例:
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers(100); // 预先分配足够的内存
for (size_t i = 0; i < numbers.size(); ++i) {
numbers[i] = i;
}
return 0;
}