阅读:104
程序启动后会将内存空间划分成这样的几块区域,便于更好的管理。
heap
):运行时按需开辟的空间,从堆低向上开辟空间,堆顶向下释放空间。在程序结束后也被操作系统会自动回收。值得注意的是,字符串数组char2
的内容"abcd"
也是存储在栈上的,是从常量区拷贝过来的。
void* malloc ( size_t size );
malloc
返回通用类型的指针,将其强制转换为所需类型,并用该类型的指针维护该内存空间。NULL
。void* calloc ( size_t num, size_t size );
calloc
函数在堆区上申请num
个size
大小的空间,返回起始地址并将内容初始化为0。
void* realloc ( void* memblock, size_t size );
realloc
函数为已开辟的空间重新开辟大小。
NULL
。void free ( void* memblock );
free
函数释放指针指向的动态开辟的空间,但不对指针造成任何影响。
使用结束free
释放内存以防内存泄漏,将指针置空避免成为野指针。
防止增容失败将原空间指针置空,故不可直接使用原指针接受返回值。判断非空后再赋给原指针。
C语言的动态内存管理不够方便在细节上还需要优化,所以C++进行了一些改动。
//C语言动态申请
int* p1 = (int*)malloc(sizeof(int));
int* p2 = (int*)malloc(sizeof(int) * 5);
//动态释放
free(p1);
free(p2);
//C++动态申请
int* p3 = new int;
int* p4 = new int[5];
//动态释放
delete p3;
delete[] p4;
使用关键字new
开辟空间,不需要强制类型转换,new 后面跟的类型就是空间地址解引用后对应的类型。new和delete 操作连续空间使更加方便。
//初始化空间
int* p1 = new int(5);
char* p2 = new char('a');
A* p3 = new A;
new 类型后面可以使用()
进行初始化,按照类型的大小用相同的值初始化每块空间。
int* p = new int[5]{ 1,2,3,4,5 };
C++11支持用
{}
初始化连续空间。
如图所示,监视1对应开辟空间,监视2对应着销毁空间的时候。可见,此时 malloc free 和 new delete 没有区别。
malloc/free 和 new/delete 对内置类型没有本质区别,只有语法上的区别。
自定义类型自动调用构造函数,比粗暴初始化的 calloc 更加人性化。由于 new 定义对象时没有传参,所以必须要类有默认构造函数。
malloc/free 和 new/delete 最好匹配使用,不要随意搭配,否则可能会出错。
//1.
int* p = (int*)malloc(sizeof(int));
free(p);
//2.
int* p = new int;
delete p;
//3.
int* p = new int[5];
delete[] p;
C++之所以发明 new/delete 语法,原因有二:
new/delete 和 malloc/free 二者对于内置类型只有用法上的区别,对于自定义类型,会自动调用自定义类型的构造函数和析构函数。这是二者最大的区别,也是发明 new/delete 的目的。
class Stack {
public:
Stack(int capacity = 4)
: _capacity(capacity)
, _top(0)
{
_a = new int[_capacity]; //不需要判空
}
~Stack() {
delete[] _a;
_capacity = _top = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
//动态开辟,主动控制生命周期
Stack* pst = new Stack(8); //开辟空间,调用构造函数
delete pst; //调用析构函数,释放空间
new/delete是进行动态开辟和释放的操作符,operator new/operator delete 是系统提供的全局函数。
new/delete的底层是分别调用operator new/operator delete 来实现的,而 operator new/operator delete 又是分别调用C语言的 malloc/free 实现的。
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
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;
}
如图所示,调用 operator new 函数只能开辟空间和抛出异常,而 new 对象时会调用 operator new 函数,并调用构造函数。
operator new/operator delete 就是对 malloc/free 的封装,operator new 可以说是符合面向对象特征的 malloc,多了抛异常的处理机制,更符合面向对象对程序异常的处理机制。operator delete 同理。
operator new/operator delete 并不是提供给用户使用的函数,是提供给 free/delete 使用的。
operator new/operator delete 的类专属重载多用于解决项目中需要频繁的申请小块内存的问题,先向系统申请一块内存池,之后开辟空间就向内存池开辟空间,以免不必要的消耗。
operator new 一般有两种形式,一是全局重载的 operator new,二是针对某个类专属定制的重载 operator new。
void* operator new (size_t size);
void* A::operator new (size_t size);
一般 operator new 就是用来分配内存的,系统默认的全局重载也是用 malloc 实现向堆上申请内存,并返回地址。
class ListNode {
public:
friend class List;
void* operator new(size_t size) {
void* p = nullptr;
p = allocator<ListNode>().allocate(1); //STL中内存池--空间配置器
cout << "memory pool allocate" << endl;
return p;
}
void operator delete(void* p) {
allocator<ListNode>().deallocate((ListNode*)p, 1);
cout << "memory pool deallocate" << endl;
}
private:
ListNode* _prev;
ListNode* _next;
int _val;
};
class List {
public:
List() {
_head = new ListNode;
_head->_prev = _head;
_head->_next = _head;
}
~List() {
ListNode* cur = _head->_next;
while (cur != _head) {
ListNode* next = cur->_next;
delete cur;
cur = next;
}
delete _head;
_head = nullptr;
}
private:
ListNode* _head;
};
new 不能被重载,其行为总是一致的。它先调用 operator new 分配内存,然后调用构造函数初始化那段空间。要实现不同的内存分配行为,应该重载函数 operator new,而不是重载操作符 new。
使用 new/delete 操作对象时,系统会优先调用类专属的重载 operator new/delete,没有的话才会调用全局的。
不管是全局还是类专属的 operator new 都是用来实现内存空间的开辟的。除此以外,operator new[]、operator delete、operator delete[] 也是可以重载的。
new/delete 的执行过程上面也已经探究过,实现原理也分为操作内置类型和自定义类型两种。
对于内置类型,new/delete 分别调用 operator new/operator delete 函数去开辟或释放空间。和 malloc/free 基本类似,不同点上面已经讲解过,这里就不在赘述。
操作单个对象 | 实现原理 |
---|---|
new | 调用 operator new 申请空间,调用构造函数构造对象 |
delete | 调用析构函数清理资源,调用 operator delete 释放空间 |
操作多个对象 | 实现原理 |
new[N] | 调用 operator new[] ,由该函数调用 operator new 申请N块空间,再调用N次构造函数构造对象 |
delete[] | 调用N次析构函数清理资源,再调用 operator delete[],由该函数调用 operator delete 释放空间 |
//1.
new(place_address) type;
//2.
new(place_address) type(initailizer_list);
定位 new 表达式是为已分配给对象的空间调用该类型的构造函数初始化该段空间。
Test* pt = (Test*)malloc(sizeof(Test));
//已开辟好的空间,调用构造函数
new(pt) Test;
new(pt) Test(1);
如上述代码所示:pt
是一开辟好的空间,使用定位 new 表达式初始化pt
,会调用对象的构造函数初始化这段空间。
//1.
A* a = new A;
//2.
A* a = (A*)operator new(sizeof(A));
new(a) A;
可以说第一种和第二种方式是等价的,所以 new 开辟对象空间相当于调用 operator new 函数开辟空间再使用定位 new 表达式。
使用定位new表达式初始化一段已开辟好的空间,不如直接使用操作符 new 来的方便。
定位 new 表达式实际上一般配合内存池使用。内存池分配的内存没有被初始化,如果分配给自定义类型的对象,需要使用定位 new 表达式进行显式调用构造函数。
构造函数不可以显式调用,所以要使用 定位 new 表达式,但析构函数可以直接调用。
// new
A* a2 = (A*)operator new(sizeof(A));
new(a2) A;
// delete
a2->~A();
operator delete(a2);
malloc/free 和 new/delete 的相同点是都是从堆上主动申请空间和释放空间,不同点是:
用法上的区别 |
---|
malloc 申请空间需要计算大小并强转类型,new 只需要在后面跟上对象类型 |
malloc 申请空间失败时会返回NULL,因此需判空,new 失败时会抛异常 |
底层上的区别 |
malloc/free 是函数,new/delete 是操作符 |
malloc/free 申请/释放空间不存在初始化,new/delete 可以初始化和销毁空间 |
内存泄漏指的是因为疏忽或错误造成程序未能释放并不可再使用的内存空间。内存泄漏不是指内存物理层面的消失,而是给程序分配内存后,因错误失去了对该段内存的控制,造成了内存的浪费。
内存泄漏可以在程序结束后返还给系统,但长期运行的程序出现内存泄漏的影响很大,如操作系统、服务器后台等,出现内存泄漏会导致程序变慢,最终卡死。
堆内存指的是程序执行中通过动态开辟的函数从堆中分配的一块内存,用完后必须通过调用相应的 free 或者 delete 释放掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间在结束运行前将无法再被使用,也就是堆内存泄漏。
指程序使用系统分配的资源,比如套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。