目录
四、operator new与operator delete函数
一、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);
}
//1. 选择题:
//选项 : A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
//globalVar在哪里?_C___ staticGlobalVar在哪里?__C__
//staticVar在哪里?_C___ localVar在哪里?__A__
//num1 在哪里?_A___
//char2在哪里?_A___ * char2在哪里?_A__
//pChar3在哪里?_A___ * pChar3在哪里?_D___
//ptr1在哪里?__A__ * ptr1在哪里?_B___
//2. 填空题:
//sizeof(num1) = __40__;
//sizeof(char2) = _5___; strlen(char2) = _4___;
//sizeof(pChar3) = _4___; strlen(pChar3) = __4__;
//sizeof(ptr1) = _4___;
3. sizeof 和 strlen 区别?
sizeof是运算符,用来计算变量占用的空间大小,单位是字节;
strlen是库函数,用来计算字符串的长度,遇到'\0'停止计算。
【说明】
1. 栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口
创建共享共享内存,做进程间通信。(后续Linux的课程会提及)
3. 堆用于程序运行时动态内存分配,堆是可以向上增长的。
4. 数据段--存储全局数据和静态数据。
5. 代码段--可执行的代码/只读常量。
二、C语言中动态内存管理方式
参考文章:【C语言】动态内存分配详解_meow_yao的博客-CSDN博客_动态内存分配
三、 C++中动态内存管理
C++中常通过new和delete操作符进行动态内存管理。
1.new/delete操作内置类型
void Test()
{
int* ptr4 = new int;//动态申请一个int类型的空间
int* ptr5 = new int(10);//动态申请一个int类型的空间并初始化为10
int* ptr6 = new int[3];//动态申请10个int类型的空间
delete ptr4;
delete ptr5;
delete[] ptr6;
}
对于内置类型,new和C语言用来动态开辟内存的malloc()差别不大,主要区别在于new可以初始化动态开辟的空间(用户自己选择,也可以只开辟空间不初始化),而malloc()不能初始化开辟的空间。
如果不小心对new动态开辟的空间进行了free()操作,且空间存放的数据是内置类型的话,不会造成内存泄漏,编译器也不会报错,但对于后面要讲到的自定义类型而言可能会导致内存泄漏(编译器不会报错)。
所以,最好要配对使用,不要交叉。即new与delete搭配,new 数据类型[]和delete[]搭配使用,malloc()和free()搭配使用。
2.new和delete操作自定义类型
class A
{
public:
A(int a = 0): _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间
//还会调用构造函数和析构函数
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);
free(p1);
delete p2;
// 内置类型是几乎是一样的
int* p3 = (int*)malloc(sizeof(int));
int* p4 = new int;
free(p3);
delete p4;
//动态开辟数组,new和delet会多次调用【自定义类型】的构造函数和析构函数
A* p5 = (A*)malloc(sizeof(A)*10);
A* p6 = new A[10];
free(p5);
delete[] p6;//不要忘记[],否则会程序崩溃
return 0;
}
对于自定义类型,new和delete区别于malloc()和free()的地方在于,new会先动态开辟空间再调用自定义类型的构造函数,delete会先调用自定义类型的析构函数再释放开辟的空间。(这里需要理解清楚,后文会进行具体的分析。)
所以,对于自定义类型变量使用new开辟空间后,如果不用delete而用的是free(),则又可能造成内存泄露)。为什么?因为没有调用自定义类型的析构函数,如果自定义类型的内部有动态开辟的操作(如果没有,则不会有影响,但是不是一个好习惯),则无法释放其开辟的堆上的空间,会导致内存泄漏。
四、operator new与operator delete函数
new和delete是用户进行动态内存申请和释放的操作符;operator new 和operator delete是
系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过
operator delete全局函数来释放空间。
函数内容(浅看一下):
/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空间不足应对措施,如果用户设置了空间不足应对措施,则继续申请,否则抛异常。
*/
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 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)
由上可知,operator new是malloc()函数的封装,operator delete是free()的封装。
注意:operator new和operator delete函数不涉及重载,不要被operator迷惑,此处的operator只是函数名的一部分。
五、new和delete的实现原理
1.内置类型
如果申请的是内置类型的空间,new和malloc(),delete和free()基本类似,不同的地方是:
new在申请空间失败时会抛异常,malloc会返回NULL。(new绝大多数情况下不会申请失败)
2.自定义类型
new : 先调用operator new()申请空间,再调用构造函数。
delete: 先调用析构函数,再释放空间。
new T[N]:申请N个自定义类型对象的空间,再调用N次构造函数。
delete[] : 先调用N次析构函数,再调用operator delete()释放空间。
六、定位new表达式(placement-new)
功能:定位new表达式用于给已经开辟好的空间调用自定义类型的构造函数定义对象。
使用格式:
new (开辟好的空间的首地址) type或者new (开辟好的空间的首地址) type(初始化列表)
使用场景:
定位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()
{
A* p1 = (A*)malloc(sizeof(A)); //p1现在指向的只不过是与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;
}
七、总结
本文主要介绍了new和delete的使用功能和实现原理。