stack 栈
stack是存在于某作用域(scope)的一块内存空间(memory space)。例如当调用函数时,函数本身就会形成一个stack用来放置他所接受的参数,以及返回地址。在函数体(function body)内声明的任何变量,其所使用的内存块都取自上述的stack。
heap 堆
heap又称System heap,是指由操作系统提供的一块global内存空间。程序可动态分配(dynamic allocated)从中获得若干区块(block)
{
Complex c1(1,2); //c1所占的空间在栈stack上
Complex *p = new Complex(1,2);// Complex(1,2)是一个临时变量,它所占的内存空间是使用new关键字从heap堆中动态分配得来的,
// 并且这块内存由指针p指向
}
栈和堆上各种对象的生命周期
stack object 栈对象的声明周期
上面例子中的C1就是stack object(存放在栈上的对象),其生命在作用与结束之际结束,这种作用域内的对象又称auto object或local object,因为他会被“自动清理”。
static object 静态对象的生命周期
static object(静态对象)其生命在作用域结束之后仍然存在,直到整个程序结束,其才会被“清理”。出了作用域的静态对象,虽然存在但不可见。这种变量常可以用来作为计数器,用来记录程序经过作用域的次数。如下:
int count_function(){
static cnt = 0;//static object在被定义的时候初始化,且只初始化这一次,再次进入函数会略过此句。
cnt++;
return cnt;
}
global object 全局对象的生命周期
写在任何作用域之外(或称为在全局域中)的对象叫做global object(全局对象)。其生命在程序开始时开始,程序结束时结束,可以把他看作是全局域中的静态对象。
heap object 堆对象的生命周期
{
Complex *p = new Complex(1,2);
……
delete p;
}
指针p所指向的对象就是heap object(堆对象),其生命始于new,止于delete。在其作用域内只new不delete,会发生内存泄漏。如下:
{
Complex *p = new Complex;
}
因为栈对象指针p离开作用域后就自动释放了。也就是指向堆内被分配的这块内存的指针失效了,但是这块堆上的内存仍然被标记为“已使用”。而这块内存没办法通过delete p去释放内存,将永远被占用,直至程序结束。如果这个作用域被程序多次经过,每次都会分配内存不释放,就会耗光内存,导致系统变慢,甚至程序崩溃。
new和delete
new和delete分别是在系统堆中分配和释放内存的指令,必须成对使用。除此之外还有new[] (array new)和delete[](array delete)分别是在系统堆中分配数组和释放数组的,也要成对使用。
new的编译器原理
new一个不带指针的heap object
new一个带指针的heap object
delete的编译器原理
delete一个不带指针的heap object
delete一个带指针的heap object
在VC编译器上动态分配内存的原理
cookie和补位内存块
在VC编译器中,所有动态分配的内存块的大小都是16byte的整数倍。并且都是以4byte的cookie头和4byte的cookie尾作为动态内存的起始标志。其中cookie头尾的内容一致,保存了当前动态分配的内存块的大小和使用与释放与否的标记信息。
其中要求所有内存块都是16byte的整数倍是因为转换成十六进制后最后一位都为0,如64byte:0x40,16byte = 0x10。剩下最后一位为0是为了标记此块内存是正在使用(末位置1)还是已经被释放(末位置0)。
为了让分配的内存块大小是16byte的整数倍,可能会有多余的内存需要填补,VC编译器的做法是不足16整数倍字节的部分用00000000的内存块补足(4byte一条)。
debug模式
debug模式会额外再对象实际占用的内存头和尾分别加入32byte和4byte的debug信息块,而release模式则没有此信息。
动态分配内存块计算方式
debug模式下的动态内存分配的
内存块大小=cookie头尾(4byte*2)+ debug头尾(32byte+4byte)+数据实际占用空间 (n byte)+ 补位(pad byte)
release模式下的动态内存分配
release模式下只比debug少了debug信息的头尾块:
内存块大小=cookie头尾(4byte*2)+数据实际占用空间 (n byte)+ 补位(pad byte)
类对象数组的内存占用情况
动态分配类对象数组的内存占用除了上面介绍的cookie头尾,debug头尾,补位内存还会在实际占用内存中多分配4byte用来存放数组的大小(size)
内存块大小=cookie头尾(4byte2)+ debug信息头尾 + size(4byte)+数据实际占用空间(n byte) 数组size +补位(pad byte)
下面分别用图来表示各种情况下内存占用情况:
动态分配不带pointer的单个class对象(debug模式和release模式)
debug模式
release模式
动态分配带pointer的单个class对象(debug模式和release模式)
动态分配不带pointer的class对象数组(debug模式和release模式)
动态分配带pointer的class对象数组(debug模式和release模式)
不成对使用new[]和delete[]的潜在危机
new[] 和delete[]必须成对使用,如果使用了new[]却使用delete释放,对于不带指针的类,不会有任何影响,因为数组元素对象,会自动在作用域结束时释放;但是,对于带指针的类,就会导致只调用一次析构函数,即只有数组的第一个元素里分配的动态内存得到了释放,其他元素的动态内存则发生内存泄漏。而由cookie引领的那块内存会正常通过delete释放。也就是说,对于带指针的类对象数组,带cookie的动态内存部分不会泄漏,泄漏的是类内部通过指针指向的内存部分。