C++ 动态内存分配
C++中经常涉及对于内存的操作,包括指针的使用,new操作符,还有原来C语言中有的malloc等,为了使写出来的程序更加健壮,需要对这些有比较深刻的理解能力。对于动态内存分配, 有些操作对象只在程序运行时才能确定,这样编译时就无法为他们预定存储空间,只能在程序运行时,系统根据运行时的要求进行内存分配,这种方法称为动态存储分配。所有动态存储分配都在堆区中进行。
当程序运行到需要一个动态分配的变量或对象时,必须向系统申请取得堆中的一块所需大小的存贮空间,用于存贮该变量或对象。当不再使用该变量或对象时,也就是它的生命结束时,要显式释放它所占用的存贮空间,这样系统就能对该堆空间进行再次分配,做到重复使用有限的资源。
内存的分配方式:
- 从静态存储区分配
内存在程序编译的时候就已经分配好了,这些内存空间在程序运行期间都存在。
- 在堆栈上分配
在函数执行期间,函数内部变量,包括形参等的存储单元都创建在堆栈上,函数结束的时候这些存储单元会自动释放。 堆栈内存分配运算内置在处理器的指令集中,效率很高,并且一般不存在失败的危险,但是分配的内存空间有限,可能会出现栈溢出的现象。
- 从堆栈或者自由存储空间上分配
程序在运行期间用malloc()或者new申请任意数量的内存,由程序员决定释放的时间。
malloc/free、new/delete、new[]/delete[]一定要匹配使用,否则可能会出现内存泄漏问题。
针对内置类型
函数原型
void operator delete(void *)throw();
用法
int *p2 = new int(1);//开辟了一个int类型的空间,并用1进行了初始化
int *p3 = new int[3];//开辟了具有3个int类型的空间,类似于数组(都是连续的)
delete p1;
delete p2;
delete[] p3;//注意匹配使用
针对自定义类型
自定义类型的内存分配函数的工作流程为:operator new->malloc函数->如果开辟失败,则抛出异常->如果开辟成功就调用构造函数。
对于在自定义对象的新建和释放一定要确保析构函数和构造函数能够统一,保证这个对象能够被明确的析构,从内存中清除,否则也将会产生不可预料的内存访问冲突(异常0xC0000005)。
// new_scalar.cpp
////////////////////////////////////
// new() Fallback Ordering
//
// +----------+
// |new_scalar<---------------+
// +----^-----+ |
// | |
// +----+-------------+ +----+----+
// |new_scalar_nothrow| |new_array|
// +------------------+ +----^----+
// |
// +------------+----+
// |new_array_nothrow|
// +-----------------+
void* __CRTDECL operator new(size_t const size)
{
for (;;)
{
if (void* const block = malloc(size))
{
return block;
}
if (_callnewh(size) == 0)
{
if (size == SIZE_MAX)
{
__scrt_throw_std_bad_array_new_length();
}
else
{
__scrt_throw_std_bad_alloc();
}
}
// The new handler was successful; try to allocate again...
}
}
异常0xC0000005
这种错误的意思一般是指访问了不属于自己的内存空间,这种错误出现的原因有:
- 给一个数组分配了较小的内存空间,然后又给该数组赋了一个比较大的值。
- 句柄或指针在使用前被释放。
堆栈
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续内存的空间,也就是系统在程序编译的过程种就已经确定好大小的空间,如果申请的空间超过栈的剩余空间将会提示stack overflow.
堆:是向高地址扩展的数据结构,是不连续的内存区域,堆的大小受限于计算机系统中有效的虚拟内存,比较灵活也比较大。
栈是由系统自动分配,速度较快,但是无法自定义地址。堆是由new分配内存,一般速度比较慢,容易产生内存碎片,不过用起来非常方便。
Enjoy Reading This Article?
Here are some more articles you might like to read next: