目录
4.operator new与operator delete函数
前言
在C语言阶段,已经学过动态内存管理,在C++阶段,动态内存管理相关操作进行了“升级”,本文将介绍相关升级。
核心要点
1. C/C++内存分布
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语言中动态内存管理方式
这里的管理方式只要是靠这几个函数
这是calloc的原型 void* calloc (size_t num, size_t size);
void Test ()
{
int* p1 = (int*) malloc(sizeof(int));
free(p1);
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
// 这里需要free(p2)吗?
free(p3 );
}
这里需要free(p2)吗?答案是不需要的。
因为p3是p2的realloc得来的。需要realloc p2时,一般存在两种方式
1.原地扩容
原地扩容时,p3与p2的位置相同,所以只需要释放p3即可。
2.异地扩容
异地扩容时,p3与p2位置不同。但由于是异地扩容,系统已经自动释放了p2,所以也不需要free。
3. C++内存管理方式
这是本文的核心内容。C++的内存管理主要是靠两个函数进行:new和delete。
int main()
{
// 动态申请一个int类型的空间
int* ptr4 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr5 = new int(10);
// 动态申请10个int类型的空间
// 动态申请10个int类型的空间
int sz = 10;
int* ptr6 = new int[sz];
int* ptr7 = new int[sz] {1, 2, 3};
//释放空间
delete ptr4;
delete ptr5;
delete[] ptr6;
return 0;
}
对于new操作符而言,是用来申请空间的,申请内置类型只需要new + 类型名称即可。
想要初始化内容,采用()传入初始化的数据。想要开辟多个空间,采用[]传入空间大小,采用{}完成初始化。我们初始化,只写一个{}或者不完全初始化时,剩余的部分会被初始化为0.
对于delete而言,是用来销毁空间的。销毁单个空间用delete + 指针;销毁多个空间用delete[] + 指针。
C++的祖师爷从来不会创造没用的语法。new的真正牛的地方:malloc无法初始化自定义类型,只单纯开辟空间。malloc无法调用构造函数。但是new就可以。
构造函数是无法显式调用的,这是C++的语法规定的。而我们malloc开辟出一块空间之后,无法显式调用构造函数,那这个自定义类型就是不完整的。因此才引入了new。
定义A类
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << _a << endl;
}
~A()
{
cout << "~A" << endl;
}
private:
int _a;
};
当我们开辟自定义类型的空间的时候,同样是用new + 类型去开辟空间。
下面是new对于自定义类型的用法。用法与内置类型相似。
其中传参采用了匿名对象传参(也可以采用有名对象)
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << _a << endl;
}
~A()
{
cout << "~A" << endl;
}
private:
int _a;
};
int main()
{
int main()
{
A* a = new A(1111);
A* aa = new A;
A* aaa = new A[3]{A(1), A(2)};
delete a;
delete aa;
delete[] aaa;
return 0;
我们执行程序之后,发现自动调用了构造函数和析构函数。
当然我们也可以采用内置类型转换的方式去传参。
new与delete的对比
new是先开空间,再调用构造函数。而delete是先调用析构函数,再free空间。
不写delete不会自动调用析构函数。原因:
指针是内置类型,不会自动调用析构函数。自定义类型才可以。否则就会内存泄漏。
4.operator new与operator delete函数
想要深入理解new和delete,那么这两个函数必须得理解好。对于new而言,完成了开辟空间,又完成了调用构造函数。那底层是如何实现的呢?
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函数,因此new是靠operator new来完成开辟空间的,而operator new则是malloc的一种封装。为什么要进行封装呢?这主要是C++判断空间是否开辟成功的标志与C语言不同。C语言往往是判断失败时,返回null,指针会变成空,而C++则是会抛异常。将malloc封装成operator new之后,则是为了符合抛异常的报错方式。
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的底层实现
先调用析构,再去operator delete。
{
int* pi = (int*)operator new(sizeof(int));
*pi = 3;
cout << *pi << endl;
return 0;
5.new和delete的实现原理
当我们new多个空间的时候
6. 定位new表达式(placement-new)
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << _a << endl;
}
~A()
{
cout << "~A" << endl;
}
private:
int _a;
};
int main()
{
A* a = (A*)operator new(sizeof(A));
new(a)A(3); //new + 指针 + 对象 + (参数列表)
return 0;
}
在这段代码中,我们用operator new给a开辟了一块空间,并使用定位new给这块空间调用了构造函数。
通过调试可以发现,第一行代码没有进入构造函数的调用阶段,但是第二行定位new发生了构造函数的调用。
7. 常见面试题
malloc/free和new/delete的区别
内存泄漏
void MemoryLeaks()
{
// 1.内存申请了忘记释放
int* p1 = (int*)malloc(sizeof(int));
int* p2 = new int;
// 2.异常安全问题
int* p3 = new int[10];
Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
delete[] p3;
}