目录
4. operator new与operator delete函数
1 C/C++内存分布
1.1 内存分布介绍
在C/C++程序中,内存分布通常分为以下几个主要部分:
- 代码段(Text Segment):存储程序的可执行代码。
- 数据段(Data Segment):
- 已初始化数据段(Initialized Data Segment):存储已初始化的全局和静态变量。
- 未初始化数据段(Uninitialized Data Segment 或 BSS Segment):存储未初始化的全局和静态变量。
- 堆(Heap):用于动态内存分配,程序运行时通过
malloc
、calloc
、realloc
等函数分配的内存。 - 栈(Stack):存储局部变量和函数调用信息(如返回地址、参数等)。
这种内存布局有助于程序的高效运行和内存管理。
1.2 代码介绍
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = { 1, 2, 3, 4 };
char char2[] = "abcd";
const char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof(int) * 4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
free(ptr1);
free(ptr3);
}
2. C语言中动态内存管理方式:
在 C 语言中,动态内存管理是通过几个标准库函数来实现的,这些函数主要包括 malloc
、calloc
、realloc
和 free
。这些函数都在 <stdlib.h>
头文件中定义。以下是它们的主要功能和用法:
-
malloc
函数:- 用于在堆区分配一块指定大小的内存空间。
- 语法:
void* malloc(size_t size);
- 返回一个指向分配内存的指针,如果分配失败则返回
NULL
。 - 示例:
int *ptr = (int*)malloc(10 * sizeof(int)); if (ptr == NULL) { // 处理内存分配失败 }
-
calloc
函数:- 用于在内存中分配多个元素,并将每个元素初始化为零。
- 语法:
void* calloc(size_t num, size_t size);
- 返回一个指向分配内存的指针,如果分配失败则返回
NULL
。 - 示例:
int *ptr = (int*)calloc(10, sizeof(int)); if (ptr == NULL) { // 处理内存分配失败 }
-
realloc
函数:- 用于重新分配内存,把内存块扩展到新的大小。
- 语法:
void* realloc(void* ptr, size_t new_size);
- 返回一个指向重新分配内存的指针,如果分配失败则返回
NULL
。 - 示例:
ptr = (int*)realloc(ptr, 20 * sizeof(int)); if (ptr == NULL) { // 处理内存分配失败 }
-
free
函数:- 用于释放先前通过
malloc
、calloc
或realloc
分配的内存。 - 语法:
void free(void* ptr);
- 示例:
free(ptr); ptr = NULL; // 避免悬空指针
- 用于释放先前通过
3. C++内存管理方式
3.1 new/delete操作内置类型
在 C++ 中,new
和 delete
操作符用于动态内存管理,特别是内置类型的内存分配和释放。以下是它们的基本用法和注意事项:
使用 new
操作符
-
分配单个内置类型的内存:
int* ptr = new int; // 分配一个 int 类型的内存 *ptr = 10; // 初始化
-
分配并初始化单个内置类型的内存:
int* ptr = new int(10); // 分配并初始化为 10
-
分配数组内置类型的内存:
int* arr = new int[10]; // 分配一个包含 10 个 int 类型元素的数组
使用 delete
操作符
-
释放单个内置类型的内存:
delete ptr; // 释放单个 int 类型的内存
-
释放数组内置类型的内存:
delete[] arr; // 释放数组内存
注意事项
-
异常处理:
new
操作符在内存分配失败时会抛出std::bad_alloc
异常,而malloc
则返回NULL
。因此,使用new
时可以通过try-catch
块来处理异常。try { int* ptr = new int[1000000000]; // 可能会失败 } catch (std::bad_alloc& e) { std::cerr << "Memory allocation failed: " << e.what() << std::endl; }
-
避免内存泄漏:确保每个
new
对应一个delete
,每个new[]
对应一个delete[]
,以避免内存泄漏。 -
初始化:使用
new
分配的内存未初始化(除非显式初始化),而new int(10)
这样的语法可以直接初始化内存。
3.2 new/delete操作内置类型
在 C++ 中,new
和 delete
操作符不仅可以用于内置类型,还可以用于自定义类型(类)。以下是如何使用 new
和 delete
来管理自定义类型的内存:
使用 new
操作符
-
分配单个自定义类型对象的内存:
class MyClass { public: MyClass(int value) : data(value) {} ~MyClass() {} private: int data; }; MyClass* obj = new MyClass(10); // 调用构造函数
-
分配数组自定义类型对象的内存:
MyClass* objArray = new MyClass[3]; // 调用默认构造函数
使用 delete
操作符
-
释放单个自定义类型对象的内存:
delete obj; // 调用析构函数
-
释放数组自定义类型对象的内存:
delete[] objArray; // 调用每个对象的析构函数
注意事项
- 构造函数和析构函数:使用
new
分配内存时,会自动调用构造函数进行初始化;使用delete
释放内存时,会自动调用析构函数进行清理。 - 数组分配:当使用
new[]
分配数组时,必须使用delete[]
释放内存,以确保每个对象的析构函数都被正确调用。 - 异常处理:
new
操作符在分配失败时会抛出std::bad_alloc
异常,可以使用try-catch
块来处理。
示例代码
#include <iostream>
class MyClass {
public:
MyClass(int value = 0) : data(value) {
std::cout << "Constructor called with value: " << data << std::endl;
}
~MyClass() {
std::cout << "Destructor called for value: " << data << std::endl;
}
private:
int data;
};
int main() {
// 分配单个对象
MyClass* obj = new MyClass(10);
delete obj;
// 分配对象数组
MyClass* objArray = new MyClass[3];
delete[] objArray;
return 0;
}
4. operator new与operator delete函数
在 C++ 中,new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
operator new
函数
-
基本用法:
/* operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。 */ void*__CRTDECLoperatornew(size_tsize) _THROW1(_STDbad_alloc) {// try to allocate size bytes void*p; while ((p=malloc(size)) ==0) if (_callnewh(size) ==0) { // report no memory // 如果申请内存失败了,这里会抛出bad_alloc 类型异常 staticconststd::bad_allocnomem; _RAISE(nomem); } return (p); }
- 该函数分配一块大小为
size
字节的内存,并返回一个指向这块内存的指针。 - 如果分配失败,默认情况下会抛出
std::bad_alloc
异常。
- 该函数分配一块大小为
-
示例:
void* ptr = operator new(sizeof(int) * 10); // 分配 10 个 int 的内存
-
重载:
- 可以重载
operator new
以自定义内存分配行为。例如:void* MyClass::operator new(size_t size) { void* p = ::operator new(size); // 自定义初始化或日志记录 return p; }
- 可以重载
operator delete
函数
-
基本用法:
void operator delete(void *pUserData) { _CrtMemBlockHeader * pHead; RTCCALLBACK(_RTC_Free_hook, (pUserData, 0)); if (pUserData == NULL) return; _mlock(_HEAP_LOCK); /* block other threads */ __TRY /* get a pointer to memory block header */ pHead = pHdr(pUserData); /* verify block type */ _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse)); _free_dbg( pUserData, pHead->nBlockUse ); __FINALLY _munlock(_HEAP_LOCK); /* release other threads */ __END_TRY_FINALLY return; } /* free的实现 */ #define free(p) _free_dbg(p, _NORMAL_BLOCK)
- 该函数释放由
operator new
分配的内存。 pUserData
是指向要释放的内存的指针。
- 该函数释放由
-
示例:
operator delete(ptr); // 释放内存
-
重载:
- 可以重载
operator delete
以自定义内存释放行为。例如:void MyClass::operator delete(void* ptr) { // 自定义清理或日志记录 ::operator delete(ptr); }
- 可以重载
operator new[]
和 operator delete[]
- 数组分配和释放:
void* operator new; void operator delete;
- 这些函数用于分配和释放数组内存,与
operator new
和operator delete
类似,但用于数组。
- 这些函数用于分配和释放数组内存,与
示例代码
#include <iostream>
#include <new> // 包含 operator new 和 operator delete 的声明
class MyClass {
public:
MyClass() { std::cout << "Constructor called\n"; }
~MyClass() { std::cout << "Destructor called\n"; }
void* operator new(size_t size) {
std::cout << "Custom operator new\n";
return ::operator new(size);
}
void operator delete(void* ptr) {
std::cout << "Custom operator delete\n";
::operator delete(ptr);
}
};
int main() {
MyClass* obj = new MyClass(); // 调用自定义的 operator new
delete obj; // 调用自定义的 operator delete
return 0;
}
5. new和delete的实现原理
5.1 内置类型
如果申请的是内置类型的空间, new 和 malloc , delete 和 free 基本类似,不同的地方是:new/delete 申请和释放的是单个元素的空间, new[] 和 delete[] 申请的是连续空间,而且 new 在申请空间失败时会抛异常, malloc 会返回 NULL 。
5.2 自定义类型
在 C++ 中,new
和 delete
操作符用于动态内存管理,特别是自定义类型(类)的内存分配和释放。它们的实现原理与内置类型类似,但涉及构造函数和析构函数的调用。以下是它们的实现原理和示例代码:
new
操作符的实现原理
-
内存分配:
new
操作符在底层调用operator new
函数来分配内存。operator new
通常使用malloc
来分配内存。- 如果内存分配失败,
operator new
会抛出std::bad_alloc
异常。
-
对象初始化:
- 对于自定义类型,
new
操作符会调用构造函数来初始化对象。 - 这一步是
new
操作符与malloc
函数的主要区别之一。
- 对于自定义类型,
delete
操作符的实现原理
-
对象清理:
- 对于自定义类型,
delete
操作符会调用析构函数来清理对象。 - 这一步在释放内存之前进行,以确保对象的资源被正确释放。
- 对于自定义类型,
-
内存释放:
delete
操作符在底层调用operator delete
函数来释放内存。operator delete
通常使用free
来释放内存。
示例代码
以下是一个自定义类 MyClass
,并展示了 new
和 delete
的使用:
#include <iostream>
#include <new> // 包含 operator new 和 operator delete 的声明
class MyClass {
public:
MyClass(int value) : data(value) {
std::cout << "Constructor called with value: " << data << std::endl;
}
~MyClass() {
std::cout << "Destructor called for value: " << data << std::endl;
}
private:
int data;
};
int main() {
// 使用 new 分配单个对象
MyClass* obj = new MyClass(10); // 调用构造函数
delete obj; // 调用析构函数并释放内存
// 使用 new 分配对象数组
MyClass* objArray = new MyClass[3]; // 调用默认构造函数
delete[] objArray; // 调用析构函数并释放数组内存
return 0;
}
代码解释
- 构造函数和析构函数:在分配和释放内存时,分别调用构造函数和析构函数。
- 自定义
operator new
和operator delete
:可以通过重载这些操作符来自定义内存分配和释放行为。
6. 定位new表达式(placement-new)
在 C++ 中,定位 new
表达式(placement new)是一种特殊的内存分配方式,它允许在已经分配好的内存上构造对象。通常情况下,new
操作符会在堆上分配内存并调用构造函数来初始化对象,而定位 new
允许我们在已有的内存空间上构造对象。
基本语法
void* operator new(size_t size, void* ptr) noexcept;
size
:要分配的内存大小,通常是对象的大小。ptr
:指向已分配好的内存空间的指针。
示例代码
以下是一个使用定位 new
的示例:
#include <iostream>
#include <new> // 包含 placement new 的声明
class MyClass {
public:
MyClass(int value) : data(value) {
std::cout << "Constructor called with value: " << data << std::endl;
}
~MyClass() {
std::cout << "Destructor called for value: " << data << std::endl;
}
private:
int data;
};
int main() {
// 预先分配内存
char buffer[sizeof(MyClass)];
// 使用定位 new 在已有内存上构造对象
MyClass* obj = new (buffer) MyClass(42);
// 调用对象的方法
obj->~MyClass(); // 显式调用析构函数
return 0;
}
注意事项
- 手动调用析构函数:使用定位
new
构造的对象需要手动调用析构函数,因为delete
不会释放由定位new
分配的内存。- 内存管理:确保
ptr
指向的内存足够大且未被其他对象占用,否则可能导致未定义行为。- 内存对齐:确保
ptr
指向的内存地址满足对象的对齐要求。
应用场景
定位 new 常用于内存池或固定内存区域中管理对象,以提高性能和内存管理的效率