C++内存管理
1.内存分布
在C++中的内存分布主要是:
(1)栈又叫堆栈,存储非静态局部变量、函数参数、返回值等
(2)内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可以使用系统接口创建共享共享内存,做进程间的通信。
(3)堆用于程序运行时动态内存分配
(4)数据段—存储全局数据和静态数据
(5)代码段—可执行的代码/只读常量
2.C语言中动态内存管理方式
2.1 malloc/calloc/realloc/free
void test()
{
int* ptr1 = (int*)malloc(sizeof(int)* 4);
free(ptr1);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int)* 4);
free(ptr3);
}
(1)malloc()
函数声明:void* malloc(unsigned size);
申请空间:在内存的动态存储区中分配一块长度为size字节的连续区域,参数size为需要内存空间的长度,返回该区域的首地址.如果申请失败,返回NULL
(2)calloc()
函数声明:void* calloc(size_t numElements, size_t sizeOfElement);
申请空间: 在内存中申请numElementssizeOfElement字节大小的连续地址空间。
sizeOfElement为申请地址的单位元素长度 numElements为元素个数。calloc函数的分配的内存也需要自行释放。如果申请失败,返回NULL
(3)realloc()
函数声明:void realloc(void* ptr, unsigned newsize);
申请空间: 给一个已经分配了地址的指针重新分配空间,参数ptr为原有的空间地址,newsize是重新申请的地址长度.如果申请失败,则返回NULL。
(4)free()
函数声明: void free(void *ptr);
作用:与malloc()函数配对使用,释放malloc函数申请的动态内存。free函数只是释放指针指向的内容,而该指针仍然指向原来指向的地方。
(5)malloc、calloc、realloc区别
1、malloc()函数分配得到的内存空间是未初始化的。因此,一般在使用该内存空间时,要调用另一个函数memset来将其初始化为全0。
2、calloc函数得到的内存空间是经过初始化的,其内容全为0。calloc函数适合为数组申请空间,可以将size设置为数组元素的空间长度,将n设置为数组的容量。
3、realloc可以对给定的指针所指的空间进行扩大或者缩小,无论是扩张或是缩小,原有内存的中内容将保持不变。
4、realloc是从堆上分配内存的,当扩大一块内存空间时,realloc()试图直接从堆上现存的数据后面的那些字节中获得附加的字节,如果能够满足,自然天下太平;如果数据后面的字节不够,问题就出来了,那么就使用堆上第一个有足够大小的自由块,现存的数据然后就被拷贝至新的位置,而老块则放回到堆上.这句话传递的一个重要的信息就是数据可能被移动。
3.C++动态内存管理
3.1 在C++中我们一般利用:new和delete操作符进行动态内存管理。
(1)new/delete申请和释放单个元素空间
(2)new[]、delete[]申请和释放一段连续空间的元素
int main()
{
int* p1 = new int;
int* p2 = new int(10);
int* p3 = new int[4];
delete() p1;
delete() p2;
delete[] p3;
return 0;
}
3.2 new/delete与malloc/free区别
class Test
{
public:
Test()
:_data(0)
{
cout << "Test()" << this << endl;
}
~Test()
{
cout << "~Test()" << this << endl;
}
private:
int _data;
};
void test1()
{
//申请单个Test类型的空间
Test* p1 = (Test*)malloc(sizeof(Test));
free(p1);
//申请4个Test类型的空间
Test* p2 = (Test*)malloc(sizeof(Test)* 4);
free(p2);
}
void test2()
{
//申请单个Test类型的空间
int* p3 = new int;
delete() p3;
//申请4个Test类型的空间
int* p4 = new int[4];
delete[] p4;
}
通过上述代码在VS编译器中运行可以发现,malloc申请的空间只是与对象大小相同的空间,而new申请空间的同时还会调用构造函数,使之成为真正的对象,并且delete释放空间时会调用析构函数,清除空间的内容,之后再释放空间。而free不会。
new/delete与malloc/free区别
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在释放空间前会调用析构函数完成空间中资源的清理
4.new和delete的实现原理
4.1 内置类型
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和 释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常, malloc会返回NULL。
对于malloc/free、new/delete一定要匹配,否则会造成内存泄漏或者代码崩溃。但是在内置类型中不影响。
4.2 operator new与operator delete函数
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
(1)operator new函数
operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
(2)operator delete函数
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;
}
4.3 自定义类型
- new的原理
1、调用 operator new()函数申请空间
2、在申请空间上执行构造函数,完成对象的构造 - delete的原理
1、在空间上执行析构函数,完成对象中的资源清理工作
2、调用operator delete函数释放对象的空间 - newT[N]的原理
1、调用 operator new[]函数,在operator new[]函数中实际调用operator new函数完成N个对象空间的申请
2、在申请空间上执行N次构造函数,完成对象的构造 - delete[]的原理
1、在释放的空间上执行N次析构函数,完成N各对象中的资源清理
2、调用operator delete[]函数释放空间,实际在operator delete[]中调用operator delete来释放空间。
5.对operator new和operator delete实现重载
这里是利用链表,对他们重载,实现链表节点使用内存池申请和释放内存,提高效率。
void* operator new(size_t n)
{
void* p = nullptr;
p = allocator<ListNode>().allocate(1);
p = allocator<ListNode>().allocate(1);
cout << "memory pool allocate" << endl;
return p;
}
void operator delete(void* p)
{
allocator<ListNode>().deallocate((listNode*)p, 1);
cout << "memory pool deallocate" << endl;
}