文章目录
- 📝new和delete操作自定义类型
- 🌠 operator new与operator delete函数
- 🌉operator new与operator delete函数
- 🌠new和delete的实现原理
- 🌉内置类型
- 🌉自定义类型
- 🌠定位new表达式(placement-new)
- 🚩总结
📝new和delete操作自定义类型
我们先看malloc
与free
,调试可以发现并不会调用析构函数
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
A* p1 = (A*)malloc(sizeof(A));
free(p1);
return 0;
}
再看new
和delete
A* p2 = new A(1);
delete p2;
总结:
new/delete
和 malloc/free
最大区别是 new/delete
对于【自定义类型】除了开空间还会调用构造函数和析构函数
而对于内置类型几乎是一样的
int* p3 = (int*)malloc(sizeof(int));
int* p4 = new int;
free(p3);
delete p4;
这是汇编一览图:
此时多出来了一个operator new
这是什么,为什么new会去调用operator new
?
00482C36 call operator new(048114Ah )
🌠 operator new与operator delete函数
🌉operator new与operator delete函数
new
和delete
是用户进行动态内存申请和释放的操作符,operator new
和operator delete
是系统提供的全局函数,new
在底层调用operator new
全局函数来申请空间,delete
在底层通过operator delete
全局函数来释放空间。
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void* p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
operator new
:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
operator delete
: 该函数最终是通过free来释放空间的
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)
如抛异常例子:
double Division(int a, int b)
{
// 当b == 0时抛出异常
if (b == 0)
throw "Division by zero condition!";
else
return ((double)a / (double)b);
}
void Func()
{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
int main()
{
try
{
Func();
}
catch(const char* errmsg)
{
//当 Division(len, time) 函数抛出这种异常时,异常对象会被赋值给 errmsg 变量。
//然后,这个 catch 块会输出 errmsg 的内容,即 "Division by zero condition!"
cout << errmsg << endl;
}
catch (...)
{
//这个 catch 块用于捕获任何其他类型的未知异常。
//当 try 块中发生任何其他类型的异常时,这个 catch 块会被执行。
//它会输出 "unkown exception"。
cout << "unkown exception" << endl;
}
return 0;
}
当你输入两个数让b == 0时程序抛异常,抛出"Division by zero condition!"
他不会再回到Func()函数中的cout << Division(len, time) << endl;
而是会跳到catch(const char* errmsg)
中,异常对象会被赋值给 errmsg
变量。然后,这个 catch
块会输出 errmsg
的内容,即 "Division by zero condition!"
,程序结束。
总结:
通过上述两个全局函数的实现知道,operator new
实际也是通过malloc
来申请空间,如果malloc
申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete
最终是通过free来释放空间的。
🌠new和delete的实现原理
🌉内置类型
如果申请的是内置类型的空间,new
和malloc
,delete
和free
基本类似,不同的地方是:new/delete
申请和释放的是单个元素的空间,new[]
和delete[]
申请的是连续空间,而且new
在申请空间失败时会抛异常,malloc
会返回NULL
。
🌉自定义类型
new
的原理
- 调用
operator new
函数申请空间 - 在申请的空间上执行构造函数,完成对象的构造
delete
的原理 - 在空间上执行析构函数,完成对象中资源的清理工作
- 调用
operator delete
函数释放对象的空间
class Stack
{
public:
// 构造函数
Stack(int initialCapacity = 10)
: _a(new int[initialCapacity])
, _top(0)
, _capacity(initialCapacity)
{}
// 析构函数
~Stack()
{
delete[] _a;
}
// 压入元素
void push(int value)
{
// 如果栈已满,需要扩容
if (_top == _capacity)
{
resize(2 * _capacity);
}
// 将元素压入栈顶
_a[_top++] = value;
}
// 弹出栈顶元素
int pop()
{
// 如果栈为空,抛出异常
if (_top == 0)
{
throw runtime_error("Stack is empty");
}
// 弹出栈顶元素并返回
return _a[--_top];
}
// 判断栈是否为空
bool empty() const
{
return _top == 0;
}
// 返回栈中元素的个数
int size() const
{
return _top;
}
private:
// 扩容函数,分配新的数组空间并移动元素
void resize(int newCapacity)
{
// 分配新的数组空间
int* newArray = new int[newCapacity];
// 使用 std::move 将原数组中的元素移动到新数组中
move(_a, _a + _top, newArray);
// 释放原数组空间
delete[] _a;
// 更新数组指针和容量
_a = newArray;
_capacity = newCapacity;
}
// 存储元素的数组指针
int* _a;
// 栈顶元素的索引
int _top;
// 数组的容量
int _capacity;
};
int main()
{
// 创建一个栈
Stack* p3 = new Stack;
// 释放栈的内存
delete p3;
压入三个元素
//p3->push(1);
//p3->push(2);
//p3->push(3);
依次弹出并打印元素
//while (!p3->empty())
//{
// cout << p3->pop() << " ";
//}
//cout << endl;
return 0;
}
先析构_a
指向的空间,再释放p3
指向的空间
new T[N]
的原理
- 调用
operator new[]
函数,在operator new[]
中实际调用operator new
函数完成N
个对象空间的申
请 - 在申请的空间上执行
N
次构造函数
delete[]
的原理
3. 在释放的对象空间上执行N
次析构函数,完成N
个对象中资源的清理
4. 调用operator delete[]
释放空间,实际在operator delete[]
中调用operator delete
来释放空间
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
A* p1 = new A;
A* p2 = new A[10];
delete p1;
delete[] p2;
return 0;
}
A类只有一个4字节大小的int成员变量。那么,创建一个A类型的对象应该需要4字节的内存空间。A* p2 = new A[10];
这我们动态创建了一个包含10个A对象的数组。10 * 4 = 40 bytes
,为什么是44bite
呢?
在动态分配数组内存时,编译器通常会在实际的数组内存之前分配一些额外的空间,用于存储数组的元素个数等信息。这样做的目的是为了在执行delete[]
操作时,能够正确地调用所有元素的析构函数。
总结:都开
4byte
存储对象个数,方便delete[]
时,知道有多少个对象,要调用多少次析构函数
内置类型就没有额外开空间:
因为,内置类型已经固定好,无需调用析构函数
int* p3 = new int[10];
delete[] p3;
🌠定位new表达式(placement-new)
定位new
表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:
定位new
表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
// 定位new/replacement new
int main()
{
// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
A* p1 = (A*)malloc(sizeof(A));
new(p1)A; // 注意:如果A类的构造函数有参数时,此处需要传参
p1->~A();
free(p1);
A* p2 = (A*)operator new(sizeof(A));
new(p2)A(10);
p2->~A();
operator delete(p2);
return 0;
}
A* p1 = (A*)malloc(sizeof(A));
使用malloc()
函数分配了一块与A
类对象大小相同的内存空间,但此时p1
指向的只是一块内存空间,还不是一个真正的A
对象,因为A
的构造函数还没有被调用。
new(p1)A;
使用"定位new"的语法在已分配的内存空间上构造一个A
对象。如果A
类的构造函数有参数,需要在这里传入参数,例如new(p2)A(10);
。
p1->~A();
显式地调用A
对象的析构函数,释放对象占用的资源。
free(p1);
free()
函数释放之前使用malloc()
分配的内存空间。