节约内存的编程方法,以图形用户界面(GUI, Graphical User Interface)为基础的Windows,可以说是一个巨大的操作系统。Windows的前身是MS-DOS操作系统,最初版本可以在128KB左右的内存上运行,而想要Windows流畅运行的话,至少需要512MB的内存。而且,由于Windows具有多任务功能,在巨大的Windows操作系统中可以同时运行多个应用,因此,即使是512MB的内存,有时也无法保证流畅运行。Windows操作系统经常为内存不足所困。
许多人可能会认为,通过借助磁盘虚拟内存就可以解决内存不足的问题。而虚拟内存也确实能避免因内存不足导致的应用无法启动。不过,由于使用虚拟内存时发生的Page In和Page Out往往伴随着低速的磁盘访问,因此在这个过程中应用的运行会变得迟钝起来。想必大家也都有过在操作应用的过程中硬盘访问灯一直亮着(这时正在进行Page In和Page Out),导致应用一时无法操作的不愉快经历吧。也就是说,虚拟内存无法彻底解决内存不足的问题。
为了从根本上解决内存不足的问题,需要增加内存的容量,或者尽量把运行的应用文件变小。接下来会向大家介绍两个把应用文件变小的编程方法。虽然增加内存容量更为便捷,但是花费也高,所以大家还是需要先看一下口袋里面的银子再来做决定。
(1)通过DLL文件实现函数共有 DLL(Dynamic Link Library
)文件,顾名思义,是在程序运行时可以动态加载Library(函数和数据的集合)的文件。此外,还有一个需要大家注意的地方,那就是多个应用可以共有同一个DLL文件。而通过共有同一个DLL文件则可以达到节约内存的效果。
例如,假设我们编写了一个具有某些处理功能的函数MyFunc()。应用A和应用B都会使用这个函数。在各个应用的运行文件中内置函数MyFunc()(这个称为Static Link,静态链接)后同时运行这两个应用,内存中就存在了具有同一函数的两个程序。但这会导致内存的利用效率降低。所以,有两个同样的函数,还是有点浪费(图5-5)。
图5-5 静态链接导致内存利用效率下降
那么,如果函数MyFunc()
是独立的DLL文件而不是应用的执行文件(EXE文件),那结果会怎样呢?由于同一个DLL文件的内容在运行时可以被多个应用共有,因此内存中存在的函数MyFunc()的程序就只有1个。这样一来,内存的利用效率也就提高了。
图5-6 进行动态链接的话即可节约内存
Windows的操作系统本身也是多个DLL文件的集合体。有时在安装新应用时,DLL文件也会被追加。应用则会通过利用这些DLL文件的功能来运行。像这样,之所以要利用多个DLL文件,其中一个原因就是可以节约内存。而且DLL文件还有一个优点就是,在不变更EXE文件的情况下,只通过升级DLL文件就可以更新。
(2)通过调用_stdcall
来减小程序文件的大小
通过调用_stdcall来减小程序文件的方法,是用C语言编写应用时可以利用的高级技巧。不过,这一思路应该也可以应用在其他编程语言中,因此大家一定要记住。
C语言中,在调用函数后,需要执行栈清理处理指令。栈清理处理是指,把不需要的数据从接收和传递函数的参数时使用的内存上的栈区域中清理出去。该命令不是程序记述的,而是在程序编译时由编译器自动附加到程序中的。编译器默认将该处理附加在函数调用方。
例如,在代码清单5-1中,从函数main()中调用了函数MyFunc()。按照默认设定,栈的清理处理会附加在函数main()这一方。在同一个程序中,同样的函数可能会被多次反复调用。而如果是同样的函数,栈清理处理的内容也是一样的。由于该处理是在调用函数一方,因此就会导致同一处理被反复进行。这就造成了内存的浪费。
代码清单5-1 C语言的函数调用程序示例
//函数调用方
void main()
{
int a;
a =MyFunc(123, 456);
}
//被调用方
int MyFunc(int a, int b)
{
...
}
虽然通过调查编译器生成的机器语言执行文件就可以得知栈清理的处理内容,不过鉴于原始的机器语言不太容易理解,所以这里我们用汇编语言的代码清单将其显示了出来。将代码清单5-1中调用函数MyFunc()的部分用汇编语言来表示,就如代码清单5-2所示。最后1行的处理就是清理处理。
代码清单5-2 调用MyFunc()的部分程序(汇编语言)
push 1C8h ←将参数 456 (=1c8h)存入栈中
push 7Bh ← 将参数123 (=7Bh) 存入栈中
call @LTD+15 (MyFunc)(00401014) ←调用MyFunc()函数
add esp, 8 ←运行栈清理
C语言通过栈来传递函数的参数。push是往栈中存入数据的指令。32位CPU中,1次push指令可以存储4个字节的数据。代码清单5-2中,由于使用了两次push指令把两个参数(456和123)存入到了栈中,因此总的来说就是存储了8字节的数据。通过call指令调用函数MyFunc()后,栈中存储的数据就不再需要了。于是这时就通过add esp, 8这个指令,使存储着栈数据的esp寄存器前进8位(设定为指向高8位字节地址),来进行数据清理。由于栈是在各种情况下都可以再利用的内存领域,因此使用完毕后有必要将其恢复到原状态。上述这些操作就是栈的清理处理。另外,在C语言中,函数的返回值,是通过寄存器而非栈来返回的。
栈清理处理,比起在函数调用方进行,在反复被调用的函数一方进行时,程序整体要小一些。这时所使用的就是_stdcall。在函数前加上_stdcall,就可以把栈清理处理变为在被调用函数一方进行。把代码清单5-1中的int MyFunc(int a, int b)
部分转成int _stdcall MyFunc(int a, int b)
进行再编译后,和代码清单5-2中add esp, 8同样的处理就会在函数MyFunc()一方执行。虽然该处理只能节约3个字节(add esp, 8是机器语的3个字节)的程序大小,不过在整个程序中还是有效果的(图5-7)。
图5-7 在被调用方进行清理处理可节约内存
酷客网相关文章:
评论前必须登录!
注册