1.c/c++内存分布
在学习c语言的时候我们就已经了解到不同的数据存储在不同的空间当中,对于内存,其被划分成不同的各个区域,分别用来管理不同的数据,如下图:
可以看到内存被划分为堆区,栈区,静态区,代码段区,内存映射段,数据段。
每一种区域都存放着不同的数据。
1. 栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
我们常定义的函数,普通变量等都存放在栈区。
2. 内存映射段 是高效的 I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口
创建共享共享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下)
3. 堆用于程序运行时动态内存分配,堆是可以向上增长的。
对于数据结构中,我们常常开辟堆区的内存保存数据,在c++中也是如此且有新方法。
4. 数据段--存储全局数据和静态数据。static修饰的全局变量与定义的全局变量存放处。
5. 代码段--可执行的代码/只读常量。
其次, c++在继承c语言的基础上,对于堆区上的申请和释放做了优化,这将体现在c++类和自定义数据类型中。
2.new与delete/malloc与free
首先我们知道如何利用c语言的方式向堆区上开辟空间,我们利用malloc,cealloc,realloc这三个函数可以实现向堆区上开辟一块空间,之后我们在进行初始化,在对空间的利用完之后,利用free函数释放掉对上的空间,实现动态内存管理。
如下代码:
int main() { char* p1 = (char*)malloc(sizeof(char)*10); free(p1); char* p2 = (char*)calloc(char ("hello world"), sizeof(char)*10); char *p3 = (char*)realloc(p2, sizeof(char) * 10); free(p3); return 0; }
对于c语言的动态内存管理:
1.首先大佬认为如此的设计使得动态内存管理过于冗余,对于空间的开辟较为麻烦。
2.我们需要强制类型转换并计算出所需该类型的大小。
c++内存管理方式:
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因
此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
new/delete操作内置类型:
我们能看到对于new的一些操作及运算符重载。
对于c++:我们可以利用new来进行动态内存的申请,利用delete进行内存的释放。
void Test() { // 动态申请一个int类型的空间 int* ptr4 = new int; // 动态申请一个int类型的空间并初始化为10 int* ptr5 = new int(10); // 动态申请10个int类型的空间 int* ptr6 = new int[3]; //动态申请5个int类型的空间并初始化 int *ptr7=new int[5]{1,2,3,4,5}; delete ptr4; delete ptr5; delete[] ptr6; delete[] ptr7; }
可以看到我们可以初始化空间,并且对于多个内存的开辟也更加方便,对于大小可以自己推导,如一个整型四个字节,一个字符型一个字节。
注意:这里在开辟多个或一个空间时,delete的写法也应与之对应,否则存在释放报错。
因为c++兼容c语言,所以除了书写简便,对于内置类型,功能是一样的。
new/delete操作自定义类型
如果仅仅是因为优化写法,那么在c++设计new和delete是纯属没有必要的,真正牛逼得地方是new与delete可以操作自定义类型!!!而这与c++11类和对象密切联系着。
利用malloc与free我们只能做到对空间的开辟与释放,但无法去初始化这个自定义类型的空间。
如自定义类型A:
class A { public: A(int a = 0) : a(a) { cout << "A():" << this << endl; } ~A() { cout << "~A():" << this << endl; } void init_a(int x) { _a = x; } private: int a; };
对于自定义类型下的malloc:
void test1() { A* p1 = (A*)malloc(sizeof(A)); //无法调用构造函数来初始化,因为当前无法显式调用构造函数 p1->_a = 5;//也无法对私有数据操作,只能通过定义接口函数来初始化 p1->init_a(5);//这也是对于简单的数据,若是设计的类中数据量偌大,则直接就g了 free(p1); }
我们可以看到自定义类型的数据要实现初始化太困难且复杂,甚至没法用。
那么对于重新设计的new与delete:
int main() { // new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间 //还会调用构造函数和析构函数 A* p1 = new A; A* p2 = new A(5); A* p3 = new A[2]{1,2}; delete p1; delete p2; delete[]p3; return 0; }
对于初始化new可以再申请空间后调用构造函数,对于释放delete会调用析构函数,之后在释放空间。
operator new与operator delete函数
new 和 delete 是用户进行 动态内存申请和释放的操作符 , operator new 和 operator delete 是
系统提供的 全局函数 , new 在底层调用 operator new 全局函数来申请空间, delete 在底层通过
operator delete 全局函数来释放空间。
注意这两个作为函数(对于new和delete的实现),并非是new与delete的重载。
如下段代码:
// try to allocate size bytes void *p; while ((p = malloc(size)) == 0) 比特就业课 通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果 malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施 就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。 5. new和delete的实现原理 5.1 内置类型 if (_callnewh(size) == 0) { // report no memory // 如果申请内存失败了,这里会抛出bad_alloc 类型异常 static const std::bad_alloc nomem; _RAISE(nomem); } return (p); } 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; } /*
对于这里的new内存开辟失败的时候,这里设计利用异常进行判断。
通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果
malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施
就继续申请,否则就抛异常,程序遇到异常就直接终止了。operator delete 最终是通过free来释放空间的。
new和delete的实现原理
内置类型
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)
如果申请的是内置类型的空间, new 和 malloc , delete 和 free 基本类似,不同的地方是:
new/delete 申请和释放的是单个元素的空间, new[] 和 delete[] 申请的是连续空间,而且 new 在申
请空间失败时会抛异常, malloc 会返回 NULL 。
自定义类型
new的原理
1. 调用operator new函数申请空间
2. 在申请的空间上执行构造函数,完成对象的构造
delete 的原理
1. 在空间上执行析构函数,完成对象中资源的清理工作
2. 调用 operator delete 函数释放对象的空间
new T[N]的原理
1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对
象空间的申请
2. 在申请的空间上执行N次构造函数
delete[]的原理
1. 在释放的对象空间上执行 N 次析构函数,完成 N 个对象中资源的清理
2. 调用 operator delete[] 释放空间,实际在 operator delete[] 中调用 operator delete 来释
放空间
malloc/free和new/delete的区别
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地
方是:
1. malloc 和 free 是函数, new 和 delete 是操作符
2. malloc 申请的空间不会初始化, new 可以初始化
3. malloc 申请空间时,需要手动计算空间大小并传递, new 只需在其后跟上空间的类型即可,
如果是多个对象, [] 中指定对象个数即可
4. malloc 的返回值为 void*, 在使用时必须强转, new 不需要,因为 new 后跟的是空间的类型
5. malloc 申请空间失败时,返回的是 NULL ,因此使用时必须判空, new 不需要,但是 new 需
要捕获异常
6. 申请自定义类型对象时, malloc/free 只会开辟空间,不会调用构造函数与析构函数,而 new
在申请空间后会调用构造函数完成对象的初始化, delete 在释放空间前会调用析构函数完成
空间中资源的清理