new delete 运算符
介绍
在C++中,new和delete是用于动态内存管理的运算符。它们允许程序在运行时分配和释放内存,这对于处理不确定大小的数据结构(如链表、树等)非常重要。下面是对这两个运算符的详细讲解:
1. new 运算符
- 功能:
new运算符用于在堆上动态分配内存。它返回一个指向所分配内存的指针。 - 语法:
Type* pointer = new Type; // 分配一个Type类型的对象
Type* arrayPointer = new Type[size]; // 分配一个Type类型的数组
- 示例:
int* p = new int; // 分配一个int类型的内存
*p = 10; // 给分配的内存赋值
int* arr = new int[5]; // 分配一个包含5个int的数组
for (int i = 0; i < 5; ++i) {
arr[i] = i; // 初始化数组
}
2. delete 运算符
- 功能:
delete运算符用于释放之前通过new分配的内存。使用delete可以避免内存泄漏。 - 语法 + 示例:
delete p; // 释放之前分配的int内存
delete[] arr; // 释放之前分配的int数组内存
3. 注意事项
- 内存泄漏:如果使用
new分配的内存没有用delete释放,就会导致内存泄漏,程序的内存使用量会不断增加。 - 双重释放:对同一块内存使用
delete两次会导致未定义行为,因此在释放内存后,最好将指针设置为nullptr。 - 构造和析构:使用
new分配对象时,会调用对象的构造函数;使用delete释放对象时,会调用对象的析构函数。
用法
new可以分配单个对象的内存,也可以分配数组对象的内存;分配的内存默认是在堆区;
相关术语: new分配单个对象的内存 <=> new创建单个对象 <=> new单个对象 new分配数组对象的内存 <=> new创建数组对象 <=> new数组对象
new的时候 语法层面有三种写法,(..,), (), 没有();分别对应有参初始化,无参初始化,不初始化;
new单个对象
(1) 对象是普通变量,可以分配对应的内存
(...)直接初始化,允许;()值初始化,允许,初始化为0;- 没有
()默认初始化,允许,分配的内存未定义;
(2) 对象是类对象,会调用构造函数,如果没有对应的构造函数,就会报错
(...)直接初始化,允许,找到对应构造函数初始化,没有找到报错()值初始化,允许,调用默认构造函数初始化,没有找到报错- 没有
()默认始化,允许,调用默认构造函数初始化,没有找到报错
new数组对象
(1) new普通变量数组,可以使用()将所有对象全部初始化为0
(...)直接初始化,不允许;会报错()值初始化,允许,数组中对象全部初始化为0;- 没有
()默认初始化,允许,分配的内存未定义;
(2) new类对象数组,有没有()都一样,均使用默认构造函数,如果没有默认构造函数就 会报错
(...)直接初始化,不允许;会报错()值初始化,允许,调用默认构造函数初始化,没有找到报错- 没有
()默认初始化,允许,调用默认构造函数初始化,没有找到报错
代码:
#include <iostream>
#include <string>
class Test
{
public:
Test() {}
};
class TestA
{
public:
TestA(int i_) : i(i_) {}
private:
int i;
};
int main() {
// 1. new可以在堆上分配 单个对象 的内存 <=> new可以在堆上创建 单个对象 <=> new单个对象
// 以下对象 指 分配的对象/创建的对象/new的对象
// 1.1 对象是普通变量,分配对应的内存
int *pi = new int(10); // 堆上分配int对象的内存,直接初始化
std::cout << *pi << std::endl;
int* pk = new int(); // 堆上分配int对象的内存,值初始化为0;
std::cout << *pk << std::endl;
int *pj = new int; // 堆上分配int对象的内存,默认初始化,分配内存未定义
std::cout << *pj << std::endl;
delete pi;
delete pj;
delete pk;
// 1.2 对象是类对象,会调用对应的构造函数,如果没有对应的构造函数,就会报错
std::string *pString1 = new std::string("hello world"); // 找到对应的构造函数初始化
std::cout << *pString1 << std::endl;
std::string *pString2 = new std::string(); // 调用默认构造函数初始化
std::cout << *pString2 << std::endl;
std::string *pString3 = new std::string; // 调用默认构造函数初始化
std::cout << *pString3 << std::endl;
// 这里演示 new单个类对象,找不到对应构造函数报错
//Test* t1 = new Test(10); // 找不到对应构造函数报错
delete pString1;
delete pString2;
delete pString3;
// 2. new可以在堆上分配 数组对象 的内存 <=> new可以在堆上创建 数组对象 <=> new数组对象
// 2.1 new 普通变量 数组 可以使用()将所有对象全部初始化为 0 => 只有()初始化合法
int *p1 = new int[100](); // 分配数组对象内存,值参初始化,数组中所有对象全部初始化为0
std::cout << p1[20] << std::endl;
int *p2 = new int[100]; // 分配数组对象内存,默认初始化,分配的内存未定义
std::cout << p2[20] << std::endl;
//int* p3 = new int[100](10); // 分配数组对象内存,直接初始化,报错,语法规定不允许
//std::cout << p2[20] << std::endl;
delete[] p1;
delete[] p2;
//delete[] p3;
// 2.2 对于 类对象 数组 有没有“()”都一样,均使用默认构造函数,如果没有默认构造函数就会报错
std::string *pString4 = new std::string[100](); // 分配数组对象内存,数组中所有对象 使用默认构造函数初始化
std::cout << pString4[20] << std::endl;
std::string* pString5 = new std::string[100]; // 分配数组对象内存,数组中所有对象 使用默认构造函数初始化
std::cout << pString5[20] << std::endl;
//std::string* pString6 = new std::string[100]("hello world"); // 分配数组对象内存,有参初始化,报错,语法规定不允许
//std::cout << pString6[20] << std::endl;
// 这里演示 new类对象数组,找不到默认构造函数报错
//TestA* t2 = new TestA[100];
delete[] pString4;
delete[] pString5;
//delete[] pStirng6;
return 0;
}
运行结果:
上面代码 注释的地方都是之前提到的的问题
- 内存未定义
- 没有找到对应的构造函数
- new数组对象不允许直接初始化
总结:
- new单个对象,语法层面上有,直接/值/默认 初始化都可以;但是new数组对象上,语法层面上不允许直接初始化;
- new 单个类对象 和 new 类对象数组时,就是要找对应的构造函数,没有找到就会报错
new的所有初始化
1. 基本初始化方式
1.1 默认初始化
int* ptr1 = new int; // 未初始化,内置类型值是随机的
1.2 值初始化
int* ptr2 = new int(); // 初始化为0
1.3 直接初始化
int* ptr3 = new int(42); // 初始化为42
1.4 列表初始化(C++11引入)
int* ptr4 = new int{42}; // 使用花括号初始化
2. 对象初始化方式
2.1 默认构造函数
class MyClass {
public:
MyClass() { value = 0; }
MyClass(int x) : value(x) {}
int value;
};
// 默认构造函数
MyClass* obj1 = new MyClass();
2.2 带参数构造函数
// 带参数的构造函数
MyClass* obj2 = new MyClass(10);
2.3 列表初始化构造函数
// 列表初始化
MyClass* obj3 = new MyClass{10};
3. 数组初始化方式
3.1 默认数组初始化
// 默认初始化数组
int* arr1 = new int[5]; // 未初始化
3.2 值初始化数组
// 值初始化数组
int* arr2 = new int[5](); // 全部元素初始化为0
3.3 直接初始化数组
// 部分初始化数组
int* arr3 = new int[5]{1, 2, 3, 4, 5}; // C++11开始支持
4. 多维数组初始化
4.1 二维数组初始化
// 二维数组初始化
int** matrix1 = new int*[3];
for (int i = 0; i < 3; ++i) {
matrix1[i] = new int[4](); // 每行初始化为0
}
// 列表初始化二维数组(C++11)
int** matrix2 = new int*[2]{
new int[3]{1, 2, 3},
new int[3]{4, 5, 6}
};
5. 复杂对象初始化
5.1 复杂类的构造函数初始化
class ComplexClass {
public:
ComplexClass() = default;
ComplexClass(int a, double b, std::string c)
: x(a), y(b), str(c) {}
int x;
double y;
std::string str;
};
// 多参数构造函数初始化
ComplexClass* complex1 = new ComplexClass(10, 3.14, "Hello");
6. 智能指针初始化(现代C++推荐)
6.1 unique_ptr
#include <memory>
// 使用 make_unique
std::unique_ptr<int> uptr1 = std::make_unique<int>(42);
// 直接构造
std::unique_ptr<MyClass> uptr2 = std::make_unique<MyClass>(10);
6.2 shared_ptr
// 使用 make_shared
std::shared_ptr<int> sptr1 = std::make_shared<int>(42);
// 直接构造
std::shared_ptr<MyClass> sptr2 = std::make_shared<MyClass>(10);
7. 特殊初始化场景
7.1 placement new
// 在预分配的内存上构造对象
char buffer[sizeof(MyClass)];
MyClass* placementObj = new (buffer) MyClass(100);
完整示例代码
#include <iostream>
#include <string>
#include <memory>
class MyClass {
public:
MyClass() : value(0) {
std::cout << "默认构造函数" << std::endl;
}
MyClass(int x) : value(x) {
std::cout << "带参数构造函数: " << value << std::endl;
}
~MyClass() {
std::cout << "析构函数" << std::endl;
}
int value;
};
int main() {
// 基本类型初始化
int* a = new int; // 未初始化
int* b = new int(); // 初始化为0
int* c = new int(42); // 初始化为42
int* d = new int{42}; // 列表初始化
// 对象初始化
MyClass* obj1 = new MyClass(); // 默认构造
MyClass* obj2 = new MyClass(10); // 带参数构造
MyClass* obj3 = new MyClass{20}; // 列表初始化
// 数组初始化
int* arr1 = new int[5]; // 未初始化
int* arr2 = new int[5](); // 全0
int* arr3 = new int[5]{1, 2, 3, 4, 5}; // 部分初始化
// 智能指针
auto uptr = std::make_unique<MyClass>(30);
auto sptr = std::make_shared<MyClass>(40);
// 释放内存
delete a;
delete b;
delete c;
delete d;
delete obj1;
delete obj2;
delete obj3;
delete arr1;
delete arr2;
delete arr3;
return 0;
}
注意事项
- 使用
new分配的内存必须手动释放,否则会造成内存泄漏 - 现代 C++ 推荐使用智能指针(
unique_ptr、shared_ptr) - 不同的初始化方式适用于不同的场景
- 列表初始化(
{})提供了更严格和安全的初始化方式
建议
- 尽量使用栈上对象和智能指针
- 避免手动管理动态内存
- 使用 RAII(资源获取即初始化)原则
malloc/free 和 new/delete之间的区别
参考视频:
https://www.bilibili.com/video/BV1Qm411z7AH/?spm_id_from=333.337.search-card.all.click&vd_source=cb02f779bd17a3aad9801e0c4464dfc9
自己的理解:
背景:malloc、free c语言中库函数,new、delete是c+中操作符
(1) malloc和new的区别
内存大小的计算:new自动计算所需分配内存大小,malloc需要手动计算
返回的指针类型:new返回的是对象类型的指针,malloc返回的是void*,之后进行类型转换
分配失败后的处理:neW分配失败会抛出异常,malloc分配失败返回的是NULL;
分配区域:new是在free store上分配内存,malloc堆上分配:
(2) delete和free的区别
参数区别:delete需要对象类型的指针,free是vo1d*类型的指针:
new的简要流程
- operator new
- 申请足够的空间
- 调用构造函数,初始化成员变量
delete的简要流程
- 先调用析构函数
- operator delete
- 释放空间
引申问题:
(1) malloc是怎么分配空间的?
malloc内存分配的核心机制:
1-内存分配基本流程
void* malloc(size_t size) {
// 1. 参数检查
if (size == 0) return NULL;
// 2. 内存大小调整
size_t actual_size = adjust_size(size);
// 3. 查找可用内存块
voidmemory_block = find_free_block(actual_size);
// 4. 如果没有可用内存块,向系统申请
if (memory_block == NULL) {
memory_block = request_system_memory(actual_size);
}
// 5. 标记内存块为已使用
mark_block_used(memory_block);
return memory_block;
}
2-内存分配的关键步骤
2.1-大小调整
- 对齐内存大小(通常是8或16字节对齐)
- 增加内存块管理所需的额外空间
size_t adjust_size(size_t size) {
// 内存对齐
size_t aligned_size = (size + 7) & ~0x7;
// 额外的块管理信息
return aligned_size + BLOCK_HEADER_SIZE;
}
2.2-内存块查找策略
- 空闲链表查找
- 最佳匹配算法
void* find_free_block(size_t size) {
// 遍历空闲链表
for (free_block* block = free_list_head; block != NULL; block = block->next) {
// 找到合适大小的块
if (block->size >= size) { // 从空闲链表移除
remove_from_free_list(block);
return block;
}
}
return NULL;
}
3-系统内存申请方法
3.1-小内存申请(<128KB)
- 使用
sbrk()系统调用 - 扩展进程堆空间
void* request_small_memory(size_t size) {
// 使用sbrk()扩展堆
void new_memory = sbrk(size);
// 更新堆信息
update_heap_metadata(new_memory, size);
return new_memory;
}
3.2 大内存申请(>128KB)
- 使用
mmap()系统调用 - 直接映射虚拟内存
void* request_large_memory(size_t size) {
return mmap(NULL, // 系统分配地址
size, // 请求大小
PROT_READ | PROT_WRITE, // 读写权限
MAP_PRIVATE | MAP_ANONYMOUS,
-1, // 无文件描述符
0); // 无文件偏移
}
4-内存块管理结构
// 内存块管理结构
typedef struct memory_block {
size_t size; // 块大小
int is_free; // 是否空闲
struct memory_block* next; // 下一个块
struct memory_block* prev; // 前一个块
}
memory_block;
5-内存分配算法
5.1 空闲链表管理
- 维护空闲内存块链表
- 支持块的合并和分割
void merge_free_blocks() {
memory_block* current = free_list_head;
while (current && current->next) {
// 检查相邻块是否可以合并
if (is_contiguous_and_free(current, current->next)) {
merge_blocks(current, current->next);
}
current = current->next;
}
}
6-内存对齐技术
// 内存对齐宏
#define ALIGN(size) (((size) + sizeof(size_t) - 1) & ~(sizeof(size_t) - 1))
实际分配流程总结
-
检查请求大小
-
调整内存大小(对齐)
-
查找空闲内存块
-
如果没有合适块,向系统申请内存
- 小内存:使用
sbrk() - 大内存:使用
mmap()
- 小内存:使用
-
标记内存块为已使用
-
返回内存指针
(2) mal1oc分配的物理内存还是虚拟内存?
malloc分配的是虚拟内存
- 当你调用
malloc()时,实际上分配的是虚拟内存地址空间 - 操作系统使用虚拟内存映射机制,将虚拟内存映射到物理内存
- 只有当程序实际访问这些内存时,才会触发缺页中断,真正分配物理内存页
(3) malloc调用后是否立刻得到物理内存?
不是立即获得
- 分配虚拟内存是瞬间完成的
- 物理内存是延迟分配的(按需分页)
- 只有在程序首次读写这块内存时,操作系统才会分配真正的物理内存页
- 延迟分配示例:
intptr = malloc(1024 * sizeof(int)); // 只分配虚拟内存 // 此时没有实际的物理内存分配
ptr[0] = 42; // 首次写入时,触发缺页中断,分配物理内存
(4) free(p)怎么知道该释放多大的空间?
通过内存块的元数据信息
- malloc在分配内存时,会在内存块前面添加一个头部(metadata)
- 头部记录了内存块的大小和其他管理信息
- free()通过读取这个头部,就能知道要释放的内存大小
- 内存块结构示例
struct MemoryBlock {
size_t size; // 记录内存块大小
int is_free; // 标记是否空闲
// 其他管理信息
};
(5) free释放内存后,内存还在吗?
内存仍然存在,但被标记为可重用
- free()并不会立即将内存返回给操作系统
- 内存被放回内存管理器的空闲列表
- 下次malloc可能会重用这块内存
- 只有在特定条件下(如大块内存),才会真正归还给操作系统
- 内存管理示意:
void free(voidptr) {
// 1. 找到内存块
MemoryBlock* block = (MemoryBlock*)(ptr - sizeof(MemoryBlock));
// 2. 标记为空闲
block->is_free = 1;
// 3. 可能进行块合并
merge_adjacent_free_blocks(block);
// 4. 加入空闲链表,等待重用
add_to_free_list(block);
}
(6) malloc, free, new, delete的伪代码
C语言风格:malloc 和 free
// malloc 伪代码
void* my_malloc(size_t size) {
// 1. 参数检查
if (size == 0) return NULL;
// 2. 内存大小调整(对齐)
size_t aligned_size = ALIGN(size);
// 3. 查找空闲内存块
voidmemory = find_free_block(aligned_size);
// 4. 如果没有空闲块,向系统申请内存
if (memory == NULL) {
memory = request_system_memory(aligned_size);
}
// 5. 记录内存块元数据
if (memory) {
store_block_metadata(memory, aligned_size);
}
return memory;
}
// free 伪代码
void my_free(void* ptr) {
// 1. 空指针检查
if (ptr == NULL) return;
// 2. 获取内存块元数据
MemoryBlockHeader* header = get_block_header(ptr);
// 3. 标记内存块为可用
header->is_free = true;
// 4. 尝试合并相邻空闲块
merge_adjacent_free_blocks(header);
// 5. 可能返回系统(取决于内存管理策略)
try_return_to_system(header);
}
C++风格:new 和 delete
// new 伪代码
void* operator new(size_t size) {
// 1. 调用 malloc 分配内存
void* memory = my_malloc(size);
// 2. 内存分配失败处理
if (memory == NULL) {
throw std::bad_alloc(); // C++ 特有的异常处理
}
return memory; // 返回分配的内存指针
}
// delete 伪代码
void operator delete(void* ptr) noexcept {
// 1. 空指针检查
if (ptr == NULL) return;
// 2. 释放内存(调用 free)
my_free(ptr);
}
// new 伪代码
template <typename T, typename... Args>
T* my_new(Args&&... args) {
// 1. 调用 operator new 分配内存
void* raw_memory = operator new(sizeof(T));
// 2. 在分配的内存上调用构造函数
T* object = static_cast<T*>(raw_memory);
new (object) T(std::forward<Args>(args)...); // placement new
return object; // 返回指向新对象的指针
}
// delete 伪代码
template <typename T>
void my_delete(T* ptr) {
// 1. 空指针检查
if (ptr == NULL) return;
// 2. 调用析构函数
ptr->~T();
// 3. 调用 operator delete 释放内存
operator delete(static_cast<void*>(ptr));
}