使用delete[]时,编译器是怎么知道需要回收的内存单元的长度的?
int main
{
int *p = new int[10];
//do some work with p
delete []p;
}
指针p应该没有保存任何其所指内存单元长度的信息,
但编译器怎么保证运行时要 delete 10个int单元呢?
问题点数:100、回复次数:20Top
1 楼insulator(天外来客)回复于 2004-12-01 19:45:14 得分 5
编译器内部自己保存了Top
2 楼iicup(双杯献酒)回复于 2004-12-01 19:47:01 得分 0
在new的时候把长度记下了。
这意味着,每次new对应一次delete
而不能一次new,分几次delete
// 正确
char* p = new char[3];
delete[] p;
// 错误(不能分三次delete)
char* p = new char[3];
delete p+0;
delete p+1;
delete p+2;
Top
3 楼snow810211(阳光)回复于 2004-12-01 19:47:44 得分 0
指针p应该没有保存任何其所指内存单元长度的信息,
但编译器怎么保证运行时要 delete 10个int单元呢?
回复
------我觉得指针p,已经指向了一个大小为10个int的单元,能记录,所以delete的时候,你用delete[]p能够释放10个int的单元,而delete p仅仅是释放p所指的单元。Top
4 楼ufool()回复于 2004-12-01 19:49:35 得分 5
是的,在内存中保存了“10”,其位置大概在p所指向地址前几个字节处。我是以前在VC6中试的。Top
5 楼autoegg(哲学指引生活 && (动心忍性,增益其所不能))回复于 2004-12-01 20:04:48 得分 0
很明显是在分配的时候记录下了所分配的长度。Top
6 楼carylin(林石)回复于 2004-12-01 20:07:01 得分 0
编译器有记录.Top
7 楼leonlux(堂郎)回复于 2004-12-01 20:07:58 得分 0
那么这样呢?
int main
{
int *p = new int[10];
int *q = p;
//do some work with p
delete []q;//这里改成q了
}
q是不是也保存了长度信息?
Top
8 楼leonlux(堂郎)回复于 2004-12-01 21:19:27 得分 0
还有就是,如果p真的保存了长度,那么编译器是怎么来实现的呢?Top
9 楼cenlmmx(学海无涯苦作舟)回复于 2004-12-01 22:52:07 得分 0
就在分配空间的前面的几个字节里记录了分配的长度Top
10 楼lbaby(春天来了...)回复于 2004-12-01 23:08:20 得分 0
好像它的记录的名字叫cookieTop
11 楼notdefined(未定义)回复于 2004-12-01 23:18:39 得分 0
印象中,这个东西好象和编译器没多大关系。
内存的分配和回收,由操作系统进行的。每分配一块内存,就有一个叫做内存块描述符(好象是这名字)的结构定义内存的大小、位置、访问控制等一系列的东西。这样当应用程序将该内存块标记为废弃的时候,操作系统就会根据块描述符的描述对内存进行回收。
大体意思是这样吧,操作系统原理一类的书应该都有阐述,我是看过就忘,寒……Top
12 楼oo(为了名副其实,努力学习oo技术ing)回复于 2004-12-01 23:22:59 得分 10
分配内存的时候会记录分配内存的长度的,
这个信息在哪里跟实现有关,
vc6里,是放在p前的8个字节里。Top
13 楼OverlordBlind(OverlordBlind)回复于 2004-12-01 23:53:37 得分 10
个编译器做法不尽相同,原理差不多
你要n字节,编译器替你在 q 位置分了 n + a 字节
在a个字节中记录n,然后返回p = q + a作为给你的首地址
delete p时到q = p - a位置读取块大小,销毁之Top
14 楼whoho(在北方流浪)回复于 2004-12-02 02:53:36 得分 0
编译器不会知道的
操作系统知道,动态内存由系统进行记录Top
15 楼pandengzhe(无为之为 之 混迹苍生)回复于 2004-12-02 08:20:24 得分 0
编译器分配回收Top
16 楼redjackwong(隔壁老汪)回复于 2004-12-02 08:55:22 得分 0
运行的时候自动记录了,因为delete [ ]只能回收new sign[int]分配的内存,所以运行时遇到new就保存那个int就行了。
另外,new是动态分配内存,编译器不可能在编译时知道大小,所以不是在编译时记录下的。Top
17 楼beyondtkl(大龙驹<*好久没来了,兄弟们好吧。*>)回复于 2004-12-02 09:06:09 得分 10
是呀
new的时候 在最前头的内存有保存本次分配的字节数 这就是所谓的 memory cookie...
^_^Top
18 楼kobefly(科比--网络学习中)回复于 2004-12-02 09:14:50 得分 30
这里是delete底层实现的一些知识
看一下对你的理解会有帮助
一般说,如果
int *tmp1 = new int(2);
int *tmp2 = new int[10];
那么:
delete tmp1;
delete []tmp2;
问题是:
1.delete 如何识别 tmp2 指向的数组有10个元素?
2.如果 delete []tmp1 会导致“不可预测的结果”(Effective C++语),但是这个不可预测一般会是什么结果?是与 tmp 的物理地址相邻的内存对应的对象的破坏吗?
---------------------------------------------------------------
说一下我的猜测,期待高手。第一个问题,系统在分配内存时都有一个叫做“小甜饼”的东东,是不是可以通过它来确认内存是怎么使用的。第二个问题,既然是不可预测的那就肯定不一定是与它相邻的对象被破坏,是不是会把与它相邻的内存的内容作为指针去释放啊。
---------------------------------------------------------------
To:楼上
不一定。:)
因为有些东西C++标准并没有规定编译器怎么实现,C++标准只规定了在正确使用语法时应该达到的效果。所以对于没有规定的东西,由于各家编译器厂商可以尝试不同的处理方案,导致了结果“不可预测”。
所以,我认为“不可预测”的意思就是这个意思。在同一种编译器上,比如说VC,我想许多东西应该还是可以预测的,比如说没有任何异常或者跳出一个错误对话框,这种“预测”也只是经验性的。
---------------------------------------------------------------
以下来自http://www.csdn.net/develop/read_article.asp?id=19569
VC6下面,就会在分配的内存块其实位置放一个Cookie,,当进行delete的时候,指针前移4个字节,读出内存大小size,然后释放size+4的空间,我们可以用下面的小程序进行简单的测试:
#include <iostream>
using namespace std;
class A
{
public:
A() {cout<<"A"<<endl;}
int i;
~A() {cout<<"~A"<<endl;}
};
int main()
{
A* pA=new A[5];
int* p=(int*)pA;
*(p-1)=1;
delete []pA;
return 0;
}
---------------------------------------------------------------
好像是在分配内存时,在分配的内存块前面保留了几个字节的空间用来保存其分配大小的
---------------------------------------------------------------
Top
19 楼kobefly(科比--网络学习中)回复于 2004-12-02 09:15:00 得分 30
寄件者: "Peng Chunhua" <chpeng@psh.com.cn>
传送日期: 2002年9月12日 AM 11:00
主旨: 关於 [池内春秋] 文章的一个问题
To:侯先生
您好,一直对先生的文章很感兴趣,经常于谈笑中分析问题,让人茅塞顿开,获益非浅。谢谢。
先生在最近一期的《程序员》杂志(2002年9期)上发表了一篇文章[池内春秋]介绍了Memory Pool的技术。不过,不知道什麽原因,在《程序员》上一定要分上下篇发表。所以目前还不太清楚Memory Pool的实质内容。不过,在上篇中关於[空间上的额外开销]和[速度上的额外开销]的说法,本人有点疑问,望先生指教。
一,空间上的额外开销
先生认为在C++平台提供的内存配置工具中会带来额外的配置,即Cookie。并进行了明证。
其实我认为这个证明是有误的,首先VC6和C++Builder在内存管理上就是不一样的机制。通过分析,我发现new在底层肯定是调用malloc的(在C++平台上)。所以,只要分析一下malloc的实现机制就可以了。在VC6.0上,如果用Debug版的话,就和先生说的一样,有额外开销,基本上是32个字节,用来记录内存链表和分配内存的源文件名,行号,字节数,第几次分配的一些信息。这也就是VC6可以在Debug版可以检查内存泄漏的原因。具体叁考一下malloc的Debug版实现的代码就可以分析出来的。在VC6.0的Release版上,就没有这些额外记录了,malloc的实现是直接调用HeapAlloc的,释放也是直接调用HeapFree的,VC本身并没有对内存进行任何多馀的操作。所以在Release版中malloc的返回值等於HeapAlloc的值,而Debug中malloc的值就等於HeapAlloc() + 0x20 的值。至於内存的大小,在Release版中也是直接通过HeapSize得到的。并且在VC中同一个模块中所有的malloc, new都是在一个Heap上操作的,在VC的运行代码中是_crtheap。这也就是为什麽在申请10000000个C1的对象时VC比C++Builder中花费的时间比较长的一个原因。不过,在Release版中,申请内存的头部确实有32个字节记录了内存的一些信息,比如大小。但该Cookie和C++平台无关,也就是说,所有用HeapAlloc分配的内存在头部都有32个字节的额外开销。(好像不止32个字节,并且在申请内存的末尾也有一些标记)
在C++Builder中管理内存和VC中是不一样的。C++Buider中不用Heap进行内存的分配,而是自身通过TMemoryManager进行内存管理。具体方式是通过VirtualAlloc一次分配16KByte字节,当程序通过malloc分配内存时,C++Builder就遍历自身管理的内存,将空闲的内存进行分配,并在头部记录内存的大小。当内存不足时,再一次调用VirtualAlloc向系统申请内存,由C++Builder进行分配管理。所以,先生在空间之明证上说明C++Builder表现很好。
二,速度之明证
前面比较了VC6.0和C++Builder的内存管理上的不同,那麽,对於在速度上VC和C++Builder的差异也就不难理解了。VC中的内存分配在一个模块中都是在_crtheap上分配的,当再次申请内存时,VC中必须由系统Heap遍历整个Heap区进行申请。而C++Builder只需首先遍历自身管理的VirtualAlloc链表,发现VirtualAlloc中有空闲内存时再遍历该VirtualAlloc的内存区域。这样由於一个VirtualAlloc对应了很多malloc的内存,在遍历整个内存的时间上就比VC快了。比如:一次VirtualAlloc的内存可以管理m个malloc,那麽对已经分配了n*m个内存而言,再分配一个内存的花费为:
VC = n*m
C++Builder=n+m
同样可以推断:如果C++Builder在管理内存上一次VirtualAlloc的大小不是16K(0x4000)而是更大,在这种分配1000000个内存时速度应该更快一点。(不过,效果估计不明显)根据上面对C++Builder的内存管理分析,也就可以理解为什麽在C++Builder的程序中会出现经过一系列内存申请和释放後,通过TaskManager观察内存使用量并不一定回到执行这些操作之前了。因为VirtualAlloc中的某一块内存被使用的话,即使其他内存都被释放了,C++Builder也不能将该内存提交给系统释放。
对於GCC的编译器我没有研究过,在此不敢发表看法。
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
const double PRECISION = 1E-6;
const int COUNT_OF_NUMBER = 4;
const int NUMBER_TO_BE_CAL = 24;
double number[COUNT_OF_NUMBER];
string expression[COUNT_OF_NUMBER];
bool Search(int n)
{
if (n == 1) {
if ( fabs(number[0] - NUMBER_TO_BE_CAL) < PRECISION ) {
cout << expression[0] << endl;
return true;
} else {
return false;
}
}
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
double a, b;
string expa, expb;
a = number[i];
b = number[j];
number[j] = number[n - 1];
expa = expression[i];
expb = expression[j];
expression[j] = expression[n - 1];
expression[i] = '(' + expa + '+' + expb + ')';
number[i] = a + b;
if ( Search(n - 1) ) return true;
expression[i] = '(' + expa + '-' + expb + ')';
number[i] = a - b;
if ( Search(n - 1) ) return true;
expression[i] = '(' + expb + '-' + expa + ')';
number[i] = b - a;
if ( Search(n - 1) ) return true;
expression[i] = '(' + expa + '*' + expb + ')';
number[i] = a * b;
if ( Search(n - 1) ) return true;
if (b != 0) {
expression[i] = '(' + expa + '/' + expb + ')';
number[i] = a / b;
if ( Search(n - 1) ) return true;
}
if (a != 0) {
expression[i] = '(' + expb + '/' + expa + ')';
number[i] = b / a;
if ( Search(n - 1) ) return true;
}
number[i] = a;
number[j] = b;
expression[i] = expa;
expression[j] = expb;
}
}
return false;
}
void main()
{
for (int i = 0; i < COUNT_OF_NUMBER; i++) {
char buffer[20];
int x;
cin >> x;
number[i] = x;
itoa(x, buffer, 10);
expression[i] = buffer;
}
if ( Search(COUNT_OF_NUMBER) ) {
cout << "Success." << endl;
} else {
cout << "Fail." << endl;
}
}
Top
20 楼kobefly(科比--网络学习中)回复于 2004-12-02 09:15:47 得分 0
对了,上边的知识摘自FAQ, 只是转载,呵呵Top




