发信人: skyice()
整理人: skyice(2000-06-22 00:25:54), 站内信件
|
系统内存清理
16 位 Windows 版本(从 1985 年发布的 1.01 版,到 1993 年发布
的 3.11 版)共同存在的一个问题是,无力清理所有已分配的系统内存。回
想一下,这个问题源自一个糟糕的设计决策。Windows 要求应用程序管理在
系统内存中分配的对象的创建和清理。当 Win16 应用程序忘记删除对象时,
系统内存就丢失了。在那些持续运行数周或者数月而不重新启动的系统上,
这样的应用程序的行为就会导致各种系统堆之一被耗尽。最后的必然结果就
是系统以一种形式或另一种形式崩溃。
为了理解 Windows 98 和 Windows NT 如何解决这个问题,首先理解
为什么会出现问题是很有帮助的。Windows 3.1(及其早期版本)为什么不
从内存中删除系统对象的主要原因就是为使程序间能够共享 GDI 绘制对象。
为解决这个问题, Win32 规范与 Win16 有这样一点不同:Win32 不认可
进程间的 GDI 对象的共享。
在 16 位 Windows 中,所有的 GDI 对象都设计得可以在程序间自由
共享。可共享对象还包括位图、画刷、DC、字体、图元文件、调色板、画笔
和区段。对象共享的理由是节省内存;一些互操作程序可以把它们的 GDI
绘图对象组合在一起,从而减少 GDI 堆的负担。不管怎么说,六个红色画
笔比一个红色画笔要消耗更多的空间。为允许这种共享,Windows 3.1 把
所有残余的 GDI 绘制对象留在内存中。其他类型的对象,包括窗口、菜单、
光标和图标,都在应用程序终止时自动从内存中清除。实际上没有共享的残
余 GDI 对象代表着在 Windows 中永远失去的内存(至少在 Windows 再
次启动之前一直是这样)。 运行 Win16 程序时,Windows 98 和 Wi-
ndows NT 一直尊重 Win16 程序共享对象的权力。因为有缺陷的 Win16
程序会消耗系统资源并潜在地损害 Win32 程序的运行,所以可能会给这
些系统带来问题。这些操作系统以几种方式进行防卫,以免于浪费空间的
Win16 程序的破坏。
Windows 98 和 Windows NT 从 Win16 程序中删除残存 GDI 对象,
但仅当没有 Win16 程序正在运行时才进行。Windows 98 和 Windows NT
以这种方式尊重 Win16 程序的共享权力。但是当所有的 Win16 程序终止
后,由16 位程序创建的 GDI 对象会被从内存中删除。因为有缺陷而又浪
费内存的 Win16 程序正逐渐被更新的 Win32 程序所代替,所以其影响也
随时间的过去而减小。
Windows 98 有两道防线,看起来它们是 Windows 本来应该一直在做
的事。无论当 Win16 程序还是 Win32 程序试图创建 GDI 对象时,Wind-
ows 98 都不会象以前的版本那样马上创建一个新对象,而是判断是否已经
存在一个相同类型的对象。如果存在,则重用前面创建的对象。用一个引用
数来保证不会被过早删除。例如,如果一个程序申请 GDI 创建一个红色画
笔对象,而且此时系统中没有红色画笔存在,GDI 就创建一个并返回该对象
的句柄。但是如果已经存在一个红色画笔,那么 GDI 为现有红色画笔的引
用数加 1,并把现有画笔的句柄返回给调用者。无论原来的创建者还是新用
户,都不需要知道 GDI 正在启用对象共享。
偶然地,在 Windows 98 上程序间隐式共享 GDI 对象会带来关于MFC
旧版本的假的断言消息。当 MFC 程序创建 GDI 对象时,例如包装在 MFC
的 CBitmap、CBrush 或 CPen 中,MFC 调试建立方式校验句柄值的唯一
性。例如,如果程序创建了两个红色画笔,Windows 98 为两个画笔提供同
一个句柄。这样的行为是 3.1 之前的 MFC 版本所不希望的。实际上,这
些旧的 MFC 版本进行检查是为确保句柄表没有包含重复值。当发现重复以
后,MFC 以巨大而难看的消息框形式显示一个断言。你可以忽略这样的消息
(或把 MFC 升级为 Windows 98 可认知的版本。)
对于熟悉 Win16 编程的程序员来说,为 Windows 98 和 Windows NT
上的 Win32 程序提供的自动清理功能是可喜的解脱。你再也不用担心系统
堆耗尽引起的系统崩溃。所有对象基于单进程存在,当进程终止时,它的残
余对象也被删除。
系统对象的自动清理只需付出很少一点开销:Win32 GDI对象不能象在
Win16中那样在进程问自由共享。如果两个程序每个都需要使用一个红色画
笔,那么每个程序都需要创建一个属于自己的。注意,Win32 对 GDI 对象
的剪贴板共享(一个包括图元文件和位图的集合)象在 Win16 中一样提供
完整的支持。只是其内部实现改变了,这不会影响剪贴板的正常使用。
残余对象的自动清理不是马马虎虎编码的借口。你创建的所有对象都要
解决掉。有好几种方式可以用来检查代码,以确保对象被正确清除。一种建
立在 MFC 和 IDE 调试器内部。MFC 程序的调试版本保存着一个关于所有
被分配和释放的对象的列表。在程序结束时,将显示一个列表,其中包含所
有的残余对象(MFC 称之为内存泄漏),包括系统对象,也包括动态分配的
内存块。要看到这个列表,你必须在一个调试器(诸如可以显示 MFC 调试
信息的 IDE 调试器)中运行你的程序。另一个探测残余系统对象(但不包
括内在分配的数据)的工具是 Nu-Mega 的 Bounds Checker。
关于自动清理,还有一个涉及全局原子的小毛病。原子是一个唯一的整
型 ID,用来访问可变长度的字符串。Windows NT 和 Windows 98 的系统
库都在全局原子表中为已注册的系统信息和已注册的剪贴板格式创建原子。
因为这些值是在进程间共享的,所以,它们必须驻留在共享内存区。尽管这
两种操作系统都有能力清理全局原子表,但它们并不这么做。好在可用原子
值的数目很大:在 Windows 98 上为 1K,在 Windows NT 上为 16K。还
由于重复的字符串将创建相同的原子,所以如果重复运行一个程序,在原子
表中注册相同字符串但忘记清理它们,不会耗尽堆。不管怎么样,你还是应
记住这是一种系统没有很好管理的资源。
一种潜在的关于全局原子表的毛病是由使用 Windows 的动态数据交换
(DDE)的应用程序引起的。也许你已经知道,这是一种涉及窗口间消息(诸
如 DATA)发送的数据共享机制。DDE 并不为共享的数据使用原子,但为人
类可读的数据标识符使用原子,例如用字符串“A12:C52”来标识电子数据
表中某范围内的单元格。如果你在创建使用低层 DDE 来共享数据的 Wind-
ows 程序,那么一定要保证清除你创建的所有原子。(也可以使用 Windo-
ws 的 DDE 管理库 DDEML,它可以合理地清理全局原子表。)
-- ※ 来源:.月光软件站 http://www.moon-soft.com.[FROM: 202.99.93.102]
|
|