在学校的BBS上有人发贴贴出了这样一段程序:
int *data = new int[20];
for( int i = 0; i<20; i++)
data[i] = i + 1;
int *p = data;
delete []p;
现在的问题是:⑴不是用delete []data,而是用delete []p能不能释放new分配的内存?
⑵p = & data[0],然后delete []p能不能释放new分配的内存?
答案是:都能释放。
可能有人要问:如果这样的话,相当于只要delete所跟的指针指向同一位置(内存地址)就能释放了,但是delete []p知识告诉要释放指针p所指向的内存空间,并且此处是一个数组,但是具体要释放的堆块有多大(也就是数组的长度)并不知道。不错,编译器确实不知道,这是运行时候动态内存分配;运行时也不知道,当它执行的释放发现调用了new运算符进行动态分配内存块,就把相应的处理交给了内存管理模块(确切一点应该是堆区内存控制模块),然后由内存管理模块来完成所需的操作。
当要进行内存释放的时候,内存管理模块就会去重新获取当初分配此处内存所分配的空间大小。那么分配内存的时候是怎么保存内存块的大小的呢?当执行new运算符之后,如果成功的话,返回一个指向分配的内存块的起始字节地方的指针,以后对于该内存区域的访问就是通过此指针来完成的。另外,为了记录所分配的空间大小,在所分配空间的前面做了一些记录,记录了所分配空间的大小以及其它的相关信息。曾经在外文论坛上看到贴子说在所分配的内存块的前面一个字(word)保存所需的信息,但是昨天晚上跟踪程序,发现并不是如此。在所分配内存块的前一个字(4个字节)的空间,几乎都是FD FD FD FD(字节顺序由低到高,之所以说几乎,是由于随着测试分配空间的大小的变化,低字节的值有变化^_^,不过大部分好像没变化,不知道是不是昨天晚上看错了还是杂的,有待深入测试)。继续观察,不断的改变分配空间的大小,发现所分配空间的前面16个字节位置的值就是保存分配内存的大小。这样看来,执行new运算符分配堆空间,至少在返回空间的前面16个字节也是附加分配的,作为访问和删除此空间的辅助信息使用。其中前面的第16个字节的值就是分配空间大小。举个例子:如果new int[30],那么前面第16个字节的值为:30*4=120 转换为16进制为78,这个就是内存中跟踪:
78 00 00 00 01 00 00 00
01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00 06 00 00 00 ……
第二行就是分配的内存块。第一行就是紧挨着所分配内存块的前面的16个字节(字节顺序从前面到后是递增的,前面的是低字节)。猜测辅助部分的其它内容应该是些标志位什么的,暂时还不知道其它的分别代表什么作用。
对于前面的测试,把new int[30]改成new int[300],内存中的结果:
B0 04 00 00 01 00 00 00
01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00 06 00 00 00 ……
这是因为300*4=1200转换成为16进制就是4B0了。
继续测试,发现第一个双字(4个字节)用来保存分配空间大小的,以字节为单位。到次应该很名朗了,每次调用delete的时候就去读取该内存块内容获取空间大小,然后释放即可。
另外:有的可能不是用到这种方式来实现,比如说有些就是在内部建立分配内存空间的指针和分配空间大小之间的映射表(Map)。释放内存的时候就根据指针值去查映射表,获取空间大小然后进行释放。
在上面的辅助信息中,还记录了有关此指针类型的相关信息。比如上面分配的是int 空间,返回的是int *指针,如果你delete用的是char *就会跑出异常,程序如下:
int *data = new int[20];
for( int i = 0; i<20; i++)
data[i] = i + 1;
char *p = (char *) data;
delete []p; // throw exception when runnable
运行就会跑出异常:_CrtIsValidHeapPointer(const void *userData用来判断当前的指针是否是合法的本地堆指针。具体的为什么转换为char *就不是本地合法堆指针我也说不清,不过肯定一点,本地合法堆指针不但记录指针地址,而且应该记录其类型,所以不要因为有了地址就可以随便调用delete,要保证类型一致。)
上面的问题大致说到这里。
下面再说下new int 和new int[1]的差别。两者都是返回一个指针,都有头信息来保存分配空间大小,这里都是4个字节。不同的是释放的时候,一个为delete p,一个为delete []p。这个主要是习惯性问题,起始对于内置对象来说,没什么区别,但是对于自定义对象来说,由于delete在释放空间之前会先调用析构函数进行处理,如果delete []p就会调用数组中每个对象的析构函数,反之,若是delete p就只会调用第一个对象的析构函数,而且还会抛出异常。所以new数组一定要delete[]来释放。
经过测试,发送返回指针的前面第2个DWORD空间就是用来标记指针类型(好像是累加的,不是标记)的。例如:对于
整形指针: 40 00 00 00 01 00 00 00
字符指针: 14 00 00 00 01 00 00 00 2D 00 00 00 FD FD FD FD
double指针:80 00 00 00 01 00 00 00 2E 00 00 00 FD FD FD FD
不要因为标记为红色的就是用来标记指针类型的,它知识应用程序在它的地址空间中建立的一些信息,用来标记此指针是堆指针,在delete的时候(上面说的异常)会判断是不是合法的本地堆指针。
转载自:IT虾米网
|
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/18472.html