一、什么是栈(stack),什么是堆(heap)
Stack
是存在于某作用域(scope)如大括号,的一块内存空间。例如当你调用函数,函数本身即会形成一个stack用来放置他所接收的参数,以及返回地址,以及local object。
Heap
或成为system heap,是由操作系统提供的一块global内存空间(全局),程序可动态分配从中获得若干区域。用new的方式,有责任要delete
{
complex c1 (1 , 2);//c1所占用的空间来自stack
complex* p = new complex(3);
//complex(3)是个临时对象,其所占用的空间乃是以new自heap动态分配而得,并由p指向。
static complex c2 (1, 2);
}
上述两个对象离开作用域时(本例中为大括号),c1的生命自然消失,因为它在栈中。但是第二个对象必须手动delete。
不同对象的生命周期:
c1便是所谓的stack object,其生命在作用域(scope)结束之际结束。
这种作用域内的object,称为local object ,又称为 auto object,因为它会被“自动”清理。即析构函数会被自动调用。
c2便是所谓的static object(静态对象),其生命在作用域结束之后任然存在,直到整个程序结束。析构函数到整个程序结束才会被调用。
class complex { ... };
...
complex c3 (1, 2);
int main()
{
...
}
c3便是所谓global object,生命在main函数之前开始,在整个程序结束之后才结束。所谓的存在与消失,即构造函数和析构函数何时被调用。
{
complex* p = new complex;
...
delete p;
}
p所指的便是heap object,其生命在它被delete之际结束。
若没加delete p,则造成内存泄漏。
因为当作用域结束,p所指向的heap object仍然存在,但是指针p的生命却结束了,作用域之外再也看不到p(也就没机会delete p了)
new的过程:先分配memory,再调用ctor(先分配内存,再调用构造函数)
delete过程:先调用dtor,再释放memory(先调用析构函数清空指针所指的字符串那块空间(即动态分配的内存),再释放指针所占的内存)
二、宝贵的秘密
1.动态分配所得内存,在vc中的情况
在vc调试模式下,上面会多出8*4=32byte ,下面多出4byte(灰色部分),还要多出两个cookies,8byte(红色部分),一共 8+(32+4)+4*2=52。由于vc下给你的内存快一定是16的倍数,所以此时vc会给你64字节内存,所以多余的由深绿色填补。
非调试,直接运行:灰色去掉即可。8+(4*2)=16,正好是16的倍数,不需要填补。
上下cookie记录整块大小,便于系统回收时,系统知道回收多大空间
第一张图,cookie是41,对应十六进制64+1,对于程序而言是获得空间,所以是1,是malloc和free函数商量好的hhhhh。
右边两张图是字符串的动态分配内存,同理。
2.动态分配所得的数组array
对于左边complex来说:分配了三个complex类型的空间(灰色部分),3*8,在调试模式下,加上header,上面32,下面4(土黄色),再加上下cookie,最后,由于分配空间给的是一个数组,所以,vc给这个数组一个整数来记录数组元素个数。
一共是8*3+(32+4)+(4*2)+ 4 = 72 调整到16的倍数,是80,绿色填补。
非调试模式:去掉土黄色header
3.array new 一定要搭配 array delete!
加上中括号[ ]系统才知道delete的不是一整块,而是一个数组,每个元素为指针,分别指向一块空间,唤起3次析构函数,去释放空间,而没有加中括号,系统认为删除的是一整块,也就是只唤起一次析构函数,把第一条指针所指的空间清空。
析构函数执行完毕后,才会将整块母体杀掉,即删除指针本体,但是右边这种做法导致了内存泄漏。
当然不加中括号对于不带指针的complex来说是可行的,但是为了养成好习惯,还是需要搭配使用的!