虚拟进程地址空间的分段
在计算机中,不同的数据必须存放在不同的区域,内存会分出不同的区域。
栈(向低地址生长)
由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。在执⾏行行函数时,函数内局部变量量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理理器器的指令集中,效率很高,但是分配的内存相对有限。
共享区
堆(向高地址生长)
堆空间是内存分段中最大的空间。堆空间由用户自行申请/释放,在C/C++中的动态内存管理主要就是对堆空间的操作。
数据段
主要存储全局数据、静态数据。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
已经初始化数据区
存放已经初始化的全局/静态数据。
未初始化数据区
存放未初始化的全局/静态数数据。
代码段
该段内容不能被用户修改。
文本常量区
主要存放常量字符串。
程序代码区
存放程序的二进制代码。
C++的动态内存管理
在C++项目中,依然可以使用C语言中的内存管理方式,但是C++也有自己的动态内存管理方式。
new/delete
申请/释放单个对象。
new/delete的底层实现
new T;//(T类型的构造函数&析构函数同时存在)
//当使用new来创造一个对象时,程序会做如下的事情:
// 1.void* operator new(size_t size) size指T类型的大小
// {
// 调用malloc(size);
// --->申请空间成功:返回所申请空间的地址
// --->申请空间失败:检测是否内存不足的应对情况
// --->有:执行内存不足的应对措施并继续调用malloc(size)
// --->无:抛出异常:bad_malloc
//
// }
// 2.调用T类型的构造函数初始化对象
delete T;
//当执行delete T时,程序会做如下的事情:
// 1.调用T类型对应的析构函数
// 2.void operator delete(void* p) p代表指向空间起始位置的指针
// {
// 调用free(p);
// }
- 对于内置类型:new并不会去对内置类型进行初始化,这时使用malloc与new的效果相同。
- 对于自定义类型:new会去调用内置类型的构造函数来对对象进行初始化。
- 可以通过new T(参数)来对对象进行初始化。
new[]/delete[]
申请/释放对象数组。
new[]/delete[]的底层实现
new T[N]; //(构造函数&析构函数同时存在)
//当执行new T[N]时,程序会做如下的事情:
// 1.void* operator new[](size_t size) 这里的size=sizeof(T)*N+4
// {
// 调用operator new(size);
// }
// 2.将对象个数N保存在空间的前4个字节,指向空间的地址向后偏移4字节
// 调用N次构造函数
//这里多出的4字节用来保存所开辟空间中对象的个数,即为N
delete[] T;
//当程序执行delete[] T时,程序会做如下的事情:
// 1.从空间的起始位置的前4字节取出对象的个数N
// 调用N次析构函数
// 2.将指针p向前偏移4字节
// operator delete[](void* p)
// {
// 调用operator delete(p);
// }
- 对于内置类型:new[]也不会对内置类型进行初始化。
- 对于自定义类型:new[]会去调用对象的构造函数来对对象进行初始化。
- 不能使用new T[N](参数)来对对象进行初始化,new[]会调用对象的没有参数的构造函数,所以当使用new[]时,需要考虑对应对象是否函数无参构造函数或带有全缺省参数的构造函数。
- 如果在使用new[]时,如果对应对象没有无参的构造函数或全缺省参数的构造函数时,程序将不会通过编译。
定位new表达式
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:new(ptr) type或者new(ptr) type(构造函数参数)
定位new表达式常用在内存池中,因为通过内存池分配的空间并没有经过初始化,所以可以通过使用new的定位表达式在已经申请到的空间上初始化对象。
operator new()/operator delete()
new
//会抛异常的operator 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
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
- 标准C++ new失败后会抛出标准异常std::bad_malloc,从上面的代码就可以看出new是通过封装malloc实现的。
nothrow new
//不会抛异常的operator new
void* _CRTDECL operator new(size_t count, const std::nothrow_t&) _THROW0()
{
//try to allocate count bytes
void* p;
_TRY_BEGIN
p = operator new(count);
_CATCH_ALL
P = 0;
_CATCH_END
return (p);
}
- nothrow new的不同之处就在于nothrow new开辟空间失败后不会抛出异常,而会返回空指针。
placement new
inline void* _CRTDECL operator new(size_t, void* _Where) _THROW0()
{
//construct array with placement at _Where
return (_Where);
}
- placement new本身仅仅会返回传进来的指针,通常使用new的定位表达式时就会用到该接口,当定位new表达式new(ptr) T(value)执行时,new(ptr) T(value)调用placement new,还会在ptr指向的内存上调用T::T(value)。
operator delete
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;
}