前注:这是我在学校BBS上贴的一个回复,贴于此处与大家讨论.
原文是:
发信人: true (对自己更残酷一点), 信区: C_and_CPP
标 题: new出来的空间系统会不会自动回收?
发信站: 哈工大紫丁香 (Fri May 14 12:07:43 2004), 转信
当程序运行完之后,我觉得系统应该会自动回收才对,是否?
各个操作系统都一样吗? 在哪里找理论根据?
我的第一个回复是:
发信人: iamxiaohan (潇寒·System Programmer), 信区: C_and_CPP
标 题: Re: new出来的空间系统会不会自动回收?
发信站: BBS 哈工大紫丁香站 (Fri May 14 12:16:51 2004)
这主要看操作系统是怎么运作的,
对于目前一般的操作系统而言,进程在销毁时回强行回收所有分配的资源,
所以你可以不用delete,但用delete会让系统及时回收资源,这是一个良好的
编程习惯
【 在 true (对自己更残酷一点) 的大作中提到: 】
: 当程序运行完之后,我觉得系统应该会自动回收才对,是否?
: 各个操作系统都一样吗? 在哪里找理论根据?
而下面的是sun的回复
发信人: Sun (大灯泡), 信区: C_and_CPP
标 题: Re: new出来的空间系统会不会自动回收?
发信站: 哈工大紫丁香 (Fri May 14 13:08:12 2004), 转信
【 在 iamxiaohan (潇寒·System Programmer) 的大作中提到: 】
: 这主要看操作系统是怎么运作的,
: 对于目前一般的操作系统而言,进程在销毁时回强行回收所有分配的资源,
这个由参考文献吗?:)
: 所以你可以不用delete,但用delete会让系统及时回收资源,这是一个良好的
: ...................
下面贴的,就是对sun的回复,欢迎大家继续讨论或指正。
发信人: iamxiaohan (潇寒·System Programmer), 信区: C_and_CPP
标 题: Re: new出来的空间系统会不会自动回收?
发信站: BBS 哈工大紫丁香站 (Sat May 15 09:25:05 2004)
看见N本书这样说过,对于现代操作系统而言,是可以不delete的,其实new操作到
最后会被编译器链接到malloc上,是调用的malloc,而malloc是一个系统调用,
用来分配内存。因此,内存的分配最终是由操作系统而不是由编译器完成的,编译
器只是起个调用系统调用以分配内存的的动作,而真正怎么分配是由操作系统决定
的.
一般操作系统都有一块很大的自由内存区,这块自由内存区也称为堆,当你用new
分配这块内存的时候,也被称为在堆上分配,当你用new产生一个操作系统调用,
要求分配内存的时候,操作系统就检查这块堆,检索出一块大小合适的且被标记为
“未分配”的内存,并返回它的指针,然后再把这块内存标记为“可分配”,当你调
用delete的时候,操作系统再清除这块内存的”可分配“标记,以便可以再次分配
这块内存。这就是一个最最简单的内存分配动作,打个比喻,操作系统就像银
行,new就像你向银行借钱打个借条,delete就像你还钱的时候,银行取消掉借条.
以前还有问,为什么在delete后,还可以操作这块内存,而不出现段错误?从上
面的描述中可以看出,delete后的内存还是在内存空间中,只是重新被操作系统
标记为“可分配”了,因此你当然还可以对其进行操作,只不过,你并不知道它在
什么时会被分配出去,如果它被分配出去了,你对此块内存的操作就非常危险了
。
当然,一个健壮的操作系统完全可以在你访问不属于你的内存时(或者访问被标记
为"可分配"的内存时),产生一个错误中断或错误陷阱,这主要看操作系统的怎么管
理的了。一个非常健壮的操作系统也许会检测所有对堆内存的访问,如果发现你
访问了一块不是你的内存,就会出错。而这样的检测是比较费时间的,因此,有
些操作系统可以选择只对”已分配“的内存进行这样的检测,于是,在此块内存
被delete重新标记为”可分配“到被重新分配前,你可以继续对此内存进行操作,
而一旦此内存被重新分配出去,内存标记表为“已分配”,这时,你再继续使用这
块内存时,操作系统就会报错了。这样,有时后你访问已delete的内存时,不会出
错,而有时又会出错,这就是由于在先前这块内存还未被分出,操作系统不检测你对
此内存的访部是否合法,因此没出错,但下次你再访问时,这块内存已被分给其它进
程使用了,因此,操作系统会对这个内存进行检测,于是就出错了.
对于一些采用页式管理的操作系统,最底层的内存分配一次分配一页(4KB),然
后在其上实现一层高级内存管理,以便用你可以分配指定大小的内存而不是一整
页了,当一页上的所有空间全部被delete,即全部标记为“可分配”时,操作系统
就会将这页的存在标记置为“不存在”,然后将其从内存中去除,并在页表中去除
,这样,当重新访问时,CPU会产生缺页中断,操作系统会对这样的中断进行处理
,处理或许是会重新载入此页,或许是产出一个错误,因为操作系统发现你已
经delete了,无权再访问此页了,要访问,你需要重新new,因此,这就会出现一
个非常有趣的现象:你delete了一块内存,然后不停的访问,前99次都可以,而
第100次就不行了,这或许就是因为前99次时,因为此页上还有其它进程没
有delete的空间,而第100次时,其它进程洽好全部delete了,于是操作系统把此
页作废了,你再访问时,操作系统就会发现你是非法访问,于就就出错了。因此
,99次运行正确的程序,不见得第100次运行就会正确!这样做的好处就是,操作
系统只在产生缺页中断的时候,才检测内存访问是否合法,由于内存访问是很频
烦的操作,降低检测次数,当然就可以系统效率升高。
操作系统在分配内存时,会建立一个数据结构,对一块已分配的内存保留它的很
多信息,比如这块内存的首地址,大小。因此,才会有这样一种显象,当malloc
或new一块内存时,你需要指定要分配的内存大小,而当你free或delete时,却不
需要指定大小,而只需给出内存的首地址指针就可以了,这主要是因为,操作系
统只需要把这块内存重新标记为”可分配“,而其内存大小,在操作系统分配内存
时,操作系统就自动把它记录下来了。
现代操作系统,在分配内存时,还会记录内存被分配给哪个进程了,这样,当进
程在退出的时候,就完全可以强制性回收所有的分配给这个进程的空间。当然,
如果一个操作系统在分配内存时,并没有记录这块内存并分配给谁了,那么它就
不能强制回收一块内存,这时就需要你必须用delete。这样的情况主要出现在古
董级的操作系统上,因为当时的内存非常有限,不允许操作系统如此大方的用它
来存储它需要存储的信息,不过,对于现在来说,保存一下内存被分配给那个进
程了实在是小case,因此,现在你可以不太注意是否使用了delete了,不过
用delete会及时的释放内存空间,但不用delete,却只会在进程完蛋时才释放,
这其间的区别,自然就不用多说了。
不过我想提醒一下,操作系统强制回收不等于我们常常说的“垃圾回收机制!”,
看看java,它的垃圾回收机制,它是先不断的分配内存,只要能分配就分配,如
果不能分配了,它再查找一下哪些内存已经不被程序使用了,它就回收它,但操
作系统强制回收机制却没有这样的能力,它不知道哪些内存已经不被程序使用了
,对于它而言,只要是没有delete的内存,它就认为程序还在使用它。说简单一
点,对于java或C两个程序,如果都只用new而不用delete,那么java可以在程序
运行的过程中就回收内存,而c的程序却不行,只能在程序运行结束后,由操作系
统一次性强制回收,java是边用边收,程序可以永远运行,而c是只用不收,当最
后没有再能用的时候,程序不得不被杀除,这时操作系统再回收,于是,如果你
在java中用for(;;)new...,java程序可以一直运行下去,而c却总用内存用尽的
时候。
那么是否是所有的new,都可以不delete呢?这也不见得,有的操作系统会在内存
分配的时候,除了保留内存分配的指针及大小外,还要保留一个类型参数,用来
标记这个内存是进程单独使用,还是多进程共用。如果是单独使用,当然可以在
进程结束时,由操作系统自动回收;如果是多进程共用,这当然就不行了,因为
,此进程虽然结束了,但还有进程在使用,因此不能回收。
在多进程共用内存的分配上,这很大可能是由引用计数实现的,每块内存都有一
个引用计数值,最初的引用计数值是0,new一次就+1,delete一次就-1,只有当
其值为0时,才会被操作系统真正回收,这时,如果你new了,但没有delete,则
引用计数就不可能成为0,这块内存就会一直被保留,而无法被操作系统回收,这
种情况下,你就必须在new后delete了。
内存的分配是由操作系统完成的,而不是由编译器完成的,malloc,new等等,只
是一个系统调用,它调用操作系统的内存分配函数来完成内存分配的功能,而具
体怎么分配是操作系统决定的,编译器无法决定,也无法知道,因此,这是平台
相关,而编译器无关的。
各个操作系统对于内存分配的策略各有不同,反正是CPU就提供了那些功能,在这
些功能上,怎么实现以满足需求是八仙过海,各显神通,这没有参考资料,也没
有标准答案,linux为了满足它的需求,采用了一种实现方式,而windows为了满
足它的需要,采用了另一种实现方式。像pyos,我的想法是提供一个功能更强一
点的内存管理,比如,一个进程可以申请分配一个共享内存区,而只有申请分配
的内存具有写权限,而其它进程只具有读权限,这又是另外一种内存分配策略。
我想我们可以提出一个需求,然后讨论可以用什么方法实现这样的需求,可以讨
论linux或windows为什么要这样实现,而不是讨论linux或windows怎样实现,不
是讨论应当怎样实现,或者去讨论书上说怎样实现。
原文:http://purec.binghua.com/Article/ShowArticle.asp?ArticleID=130