delphi 申请内存内存申请问题

西西软件园多重安全检测下载网站、值得信赖的软件下载站!
→ delphi 中的内存管理及 Delphi7 的FastMM配置
FastMM 是适用于delphi的第三方内存管理器,在国外已经是大名鼎鼎,在国内也有许多人在使用或者希望使用,就连 Borland 也在delphi2007抛弃了自己原有的饱受指责的内存管理器,改用FastMM.但是,内存管理的复杂性以及缺乏 FastMM 中文文档导致国内许多人在使用时遇到了许多问题,一些人因此而放弃了使用,我在最近的一个项目中使用了FastMM,也因此遇到了许多问题,经过摸索和研究,终于解决了这些问题。二、为什么要用FastMM第一个原因是FastMM的性能接近与delphi缺省内存管理器的两倍,可以做一个简单的测试,运行下面的代码:varI: ITic: CS:begintic := GetTickCtryfor I := 0 to 100000 dobeginSetLength(S, I + 100);edt1.Text := S;finallySetLength(S, 0);tic := GetTickCount - TMessageDlg('Tic = ' + IntToStr(Tic), mtInformation, [mbOK], 0);在我的IBM T23笔记本上,使用FastMM4(FastMM的最新版本)用时约为3300ms,而使用缺省的内存管理器,用时约为6200ms,FastMM4的性能提高达88%.第二个原因FastMM的共享内存管理器功能使用简单可靠。当一个应用程序有多个模块(exe和dll)组成时,模块之间的动态内存变量如string的传递就是一个很大的问题,缺省情况下,各个模块都由自己的内存管理器,由一个内存管理器分配的内存也必须在这个内存管理器才能安全释放,否则就会出现内存错误,这样如果在一个模块分配的内存在另外一个模块释放就会出现内存错误。解决这个问题就需要使用到共享内存管理器,让各个模块都使用同一个内存管理器。Delphi缺省的共享内存管理器是BORLNDMM.DLL,这个内存管理器并不可靠,也常常出现问题,并且,在程序发布的时候必须连同这个DLL一起发布。而FastMM的共享内存管理器功能不需要DLL支持,并且更加可靠。第三个原因是FastMM还拥有一些帮助程序开发的辅助功能,如内存泄漏检测功能,可以检测程序是否存在未正确释放的内存等。三、出现什么问题如果我们开发的应用程序,只有一个exe模块,那么,使用FastMM是一件非常简单的事情,只需要把FastMM.pas(最新版是FastMM4.pas)作为工程文件的第一个uses单元即可,如:program TusesFastMM4,…但是,通常情况下,我们的应用程序都是由一个exe模块加上多个dll组成的,这样,当我们跨模块传递动态内存变量如string变量时,就会出问题,比如,下面的测试程序由一个exe和一个dll组成: // test.dllusesFastMM4, …;procedure GetStr(var S: const Len: Integer);beginSetLength(S, Len); // 分配内存FillChar(S[1], Len, ‘A’); exportsGetS-------------------------------------program TestPusesFastMM4, …;//------------------unit mM // 测试界面…procedure TForm1.btnDoClick(Sender: TObject);varI: IS:Begintry for I := 1 to 10000 dobeginGetStr(S, I + 1);edt1.Text := S;Application.ProcessMfinallySetLength(S, 0);当第二次执行btnDoClick过程时,就会出现内存错误,为什么这样?delphi的字符串是带引用计数的,跟接口变量一样,一旦这个引用计数为0,则会自动释放内存。在btnDoClick过程中,调用GetStr过程,用SetLength给S分配了一段内存,此时这个字符串的引用计数为1,然后执行edt1.Text := S语句,字符串的引用计数为2,循环再调用GetStr给S重新分配内存,这样原来的字符串的引用计数减1,再执行edt1.Text := S,原来的字符串引用计数为0,这时,就会被释放(注意,是在TestPrj.exe释放,而不是在Test.dll释放),但这时没有出错,当循环执行完毕之后,还有一个字符串的引用计数为2,但是执行SetLength(S, 0)之后,该字符串被edt1.Text引用,的引用计数为1,第二次执行btnDoClick时,执行edt1.Text := S时,上次的引用计数为1的字符串引用计数减一变为0,就会被释放,此时,会出现内存错误。由此,可以看到,在另一个模块释放别的模块分配的内存,并不一定马上出现内存错误,但是,如果频繁执行,则会出现内存错误,这种不确定的错误带有很大的隐蔽性,常常在调试时不出现,但实际应用时出现,不仔细分析很难找到原因。要解决这个问题,就要从根源找起,这个根源就是内存管理。一、Delphi的内存管理Delphi应用程序可以使用的有三种内存区:全局内存区、堆、栈,全局内存区存储全局变量、栈用来传递参数以及返回值,以及函数内的临时变量,这两种都是由编译器自动管理,而如字符串、对象、动态数组等都是从堆中分配的,内存管理就是指对堆内存管理,即从堆中分配内存和释放从堆中分配的内存(以下称内存的分配和释放)。我们知道,一个进程只有一个栈,因此,也很容易误以为一个进程也只有一个堆,但实际上,一个进程除了拥有一个系统分配的默认堆(默认大小1MB),还可以创建多个用户堆,每个堆都有自己的句柄,delphi的内存管理所管理的正是自行创建的堆,delphi还把一个堆以链表的形式分成多个大小不等的块,实际的内存操作都是在这些块上。delphi把内存管理定义为内存的分配(Get)、释放(Free)和重新分配(Realloc)。内存管理器也就是这三种实现的一个组合,delphi在system单元中定义了这个内存管理器TMemoryManager:PMemoryManager = ^TMemoryMTMemoryManager = recordGetMem: function (Size: Integer): PFreeMem: function (P: Pointer): IReallocMem: function (P: P Size: Integer): P由此知道,delphi的内存管理器就是一个 TMemoryManager 记录对象,该记录有三个域,分别指向内存的分配、释放和重新分配例程。System单元还定义了一个变量 MemoryManager :MemoryManager: TMemoryManager = (GetMem: SysGetMFreeMem: SysFreeMReallocMem: SysReallocMem);该变量是delphi程序的内存管理器,缺省情况下,这个内存管理器的三个域分别指向GETMEM.INC中实现的SysGetMem、SysFreeMem、SysReallocMem。这个内存管理器变量只在system.pas中可见,但是system单元提供了三个可以访问该变量的例程:// 读取内存管理器,也即读取MemoryManagerprocedure GetMemoryManager (var MemMgr: TMemoryManager);// 安装内存管理器(即用新的内存管理器替换缺省的内存管理器)procedure SetMemoryManager (const MemMgr: TMemoryManager);// 是否已经安装了内存管理器(即缺省的内存管理器是否已经被替换)function IsMemoryManagerSet: B四、共享内存管理器什么是共享内存管理器?所谓共享内存管理器,就是一个应用程序的所有的模块,不管是exe还是dll,都使用同一个内存管理器来管理内存,这样,内存的分配和释放都是同一个内存管理器完成的,就不会出现内存错误的问题。那么如何共享内存管理器呢?由上分析,我们可以知道,既然要使用同一个内存管理器,那么干脆就创建一个独立的内存管理器模块(dll),其他的所有模块都使用这个模块的内存管理器来分配和释放内存。Delphi7默认就是采取这种方法,当我们使用向导创建一个dll工程时,工程文件会有这样一段话:{Important note about DLL memory management: ShareMem must be thefirst unit in your library's USES clause AND your project's (selectProject-View Source) USES clause if your DLL exports any procedures orfunctions that pass strings as parameters or function results. Thisapplies to all strings passed to and from your DLL--even those thatare nested in records and classes. ShareMem is the interface unit tothe BORLNDMM.DLL shared memory manager, which must be deployed alongwith your DLL. To avoid using BORLNDMM.DLL, pass string informationusing PChar or ShortString parameters. }这段话提示我们,ShareMem 是 BORLNDMM.DLL 共享内存管理器的接口单元,我们来看看这个ShareMem,这个单元文件很简短,其中有这样的声明:constDelphiMM = 'borlndmm.dll';function SysGetMem (Size: Integer): Pexternal DelphiMM name '@Borlndmm@SysGetMem$qqri';function SysFreeMem(P: Pointer): I external DelphiMM name '@Borlndmm@SysFreeMem$qqrpv';function SysReallocMem(P: P Size: Integer): P external DelphiMM name '@Borlndmm@SysReallocMem$qqrpvi';这些声明保证了BORLNDMM.DLL将被静态加载。在ShareMem的Initialization是这样的代码:if not IsMemoryManagerSet thenInitMemoryM首先判断内存管理器是否已经被安装(也即是否默认的内存管理器被替换掉),如果没有,则初始化内存管理器,InitMemoryManager也非常简单(把无用的代码去掉了):procedure InitMemoryMvarSharedMemoryManager: TMemoryMMM: Ibegin// force a static reference to borlndmm.dll, so we don't have to LoadLibrarySharedMemoryManager.GetMem:= SysGetMMM: = GetModuleHandle (DelphiMM);SharedMemoryManager.GetMem:= GetProcAddress (MM,'@Borlndmm@SysGetMem$qqri');SharedMemoryManager.FreeMem:= GetProcAddress (MM,'@Borlndmm@SysFreeMem$qqrpv');SharedMemoryManager.ReallocMem:= GetProcAddress (MM, '@Borlndmm@SysReallocMem$qqrpvi');SetMemoryManager (SharedMemoryManager);这个函数定义了一个内存管理器对象,并设置域指向Borlndmm.dll的三个函数实现,然后调用SetMemoryManager来替换默认的内存管理器。这样,不管那个模块,因为都要将ShareMem作为工程的第一个uses单元,因此,每个模块的ShareMem的Initialization都是最先被执行的,也就是说,每个模块的内存管理器对象虽然不相同,但是,内存管理器的三个函数指针都是指向Borlndmm.dll的函数实现,因此,所有模块的内存分配和释放都是在Borlndmm.dll内部完成的,这样就不会出现跨模块释放内存导致错误的问题。那么,FastMM又是如何实现共享内存管理器呢?FastMM采取了一个原理上很简单的办法,就是创建一个内存管理器,然后将这个内存管理器的地址放到一个进程内所有模块都能读取到的位置,这样,其他模块在创建内存管理器之前,先查查是否有别的模块已经把内存管理器放到这个位置,如果是则使用这个内存管理器,否则才创建一个新的内存管理器,并将地址放到这个位置,这样,这个进程的所有模块都使用一个内存管理器,实现了内存管理器的共享。而且,这个内存管理器并不确定是哪个模块创建的,所有的模块,只要将FastMM作为其工程文件的第一个uses单元,就有可能是这个内存管理器的创建者,关键是看其在应用程序的加载顺序,第一个被加载的模块将成为内存管理器的创建者。那么,FastMM具体是如何实现的呢?打开 FastMM4.pas(FastMM的最新版本),还是看看其Initialization部分:{Initialize all the lookup tables, etc. for the memory manager}InitializeMemoryM{Has another MM been set, or has the Borland MM been used? If so, this fileis not the first unit in the uses clause of the project's .dpr file.}if CheckCanInstallMemoryManager thenbeginInstallMemoryMInitializeMemoryManager 是初始化一些变量,完成之后就调用CheckCanInstallMemoryManager检测FastMM是否是作为工程的第一个uses单元,如果返回True,则调用InstallMemoryManager安装FastMM自己的内存管理器,我们按顺序摘取该函数的关键性代码进行分析:{Build a string identifying the current process}LCurrentProcessID: = GetCurrentProcessId;for i := 0 to 7 doUniqueProcessIDString [8 - i]:= HexTable [((LCurrentProcessID shr (i * 4)) and $F)];MMWindow: = FindWindow ('STATIC', PChar (@UniqueProcessIDString [1]));首先,获取该进程的ID,并转换为十六进制字符串,然后查找以该字符串为窗口名称的窗口。如果进程中还没有该窗口,则MMWindow 将返回0,那就,就创建该窗口:MMWindow: = CreateWindow ('STATIC', PChar (@UniqueProcessIDString [1]),WS_POPUP, 0, 0, 0, 0, 0, 0, hInstance, nil);创建这个窗口有什么用呢,继续看下面的代码:if MMWindow && 0 thenSetWindowLong (MMWindow, GWL_USERDATA, Integer (@NewMemoryManager));NewMemoryManager.Getmem: = FastGetMNewMemoryManager.FreeMem: = FastFreeMNewMemoryManager.ReallocMem: = FastReallocM查阅MSDN可以知道,每个窗口都有一个可供创建它的应用程序使用的32位的值,该值可以通过以GWL_USERDATA为参数调用SetWindowLong来进行设置,也可以通过以GWL_USERDATA为参数调用GetWindowLong来读取。由此,我们就很清楚地知道,原来FastMM把要共享的内存管理器的地址保存到这个值上,这样其他模块就可以通过GetWindowLong来获读取到这个值,从而得到共享的内存管理器:NewMemoryManager: = PMemoryManager (GetWindowLong (MMWindow, GWL_USERDATA)) ^;通过上面的分析,可以看出,FastMM 在实现共享内存管理器上要比borland巧妙得多,borland的实现方法使得应用程序必须将BORLNDMM.DLL一起发布,而FastMM的实现方法不需要任何dll的支持。但是,上面的摘录代码把一些编译选项判断忽略掉了,实际上,要使用FastMM的共享内存管理器功能,需要在各个模块编译的时候在FastMM4.pas单元上打开一些编译选项:{$define ShareMM} //是打开共享内存管理器功能,是其他两个编译选项生效的前提{$define ShareMMIfLibrary} //是允许一个dll共享其内存管理器,如果没有定义这个选项,则一个应用程序中,只有exe模块才能够创建和共享其内存管理器,由于静态加载的dll总是比exe更早被加载,因此,如果一个dll会被静态加载,则必须打开该选项,否则可能会出错{$define AttemptToUseSharedMM} //是允许一个模块使用别的模块共享的内存管理器这些编译选项在FastMM4.pas所在目录的FastMM4Options.inc文件中都有定义和说明,只不过这些定义都被注释掉了,因此,可以取消注释来打开这些编译选项,或者可以在你的工程目录下创建一个.inc文件(如FastShareMM.inc),把这些编译选项写入这个文件中,然后,在FastMM4.pas开头的“{$Include FastMM4Options.inc}”之前加入“{$include FastShareMM.inc}”即可,这样,不同的工程可以使用不同的FastShareMM.inc文件。五、多线程下的内存管理多线程环境下,内存管理是安全的吗?显然,如果不采取一定的措施,那么肯定是不安全的,borland已经考虑到这种情况,因此,在delphi的system.pas中定义了一个系统变量IsMultiThread,这个系统变量指示当前是否为多线程环境,那么,它是如何工作的?打开TThread.Create函数的代码可以看到它调用了BeginThread来创建一个线程,而BeginThread把IsMultiThread设置为了True.再来看看GETMEM.INC的SysGetMem、SysFreeMem、SysReallocMem的实现,可以看到,在开始都由这样的语句:if IsMultiThread then EnterCriticalSection(heapLock);也就是说,在多线程环境下,内存的分配和释放都要用临界区进行同步以保证安全。而FastMM则使用了一条CUP指令lock来实现同步,该指令作为其他指令的前缀,可以在在一条指令的执行过程中将总线锁住,当然,也是在IsMultiThread为True的情况下才会进行同步。而IsMultiThread是定义在system.pas的系统变量,每个模块(exe或者dll)都有自己的IsMultiThread变量,并且,默认为Fasle,只有该模块中创建了用户线程,才会把这个变量设置为True,因此,我们在exe中创建线程,只会把exe中的IsMultiThread设置为True,并不会把其他的dll模块中的IsMultiThread设置为True,但是,前面已经说过,如果我们使用了静态加载的dll,这些dll将会比exe更早被系统加载,这时,第一个被加载的dll就会创建一个内存管理器并共享出来,其他模块都会使用这个内存管理器,也就是说,exe的IsMultiThread变量没有影响到应用程序的内存管理器,内存管理器还是认为当前不是多线程环境,因此,没有进行同步,这样就会出现内存错误的情况。解决这个问题就是要想办法当处于多线程环境时,让所有的模块的IsMultiThread都设置为True,以保证不管哪个模块实际创建了内存管理器,该内存管理器都知道当前是多线程环境,需要进行同步处理。还好,windows提供了一个机制,可以让我们的dll知道应用程序创建了线程。DllMain函数是dll动态链接库的入口函数,delphi把这个入口函数封装起来,提供了一个TDllProc的函数类型和一个该类型的变量DllProc:TDLLProc = procedure (Reason: Integer); // 定义在system.pas// 定义在sysinit.pas:var DllProc: TDllP当系统调用dll的DllMain时,delphi最后会调用DllProc进行处理,DllProc可以被指向我们自己的TDllProc实现。而当进程创建了一个新线程时,操作系统会以Reason=DLL_THREAD_ATTACH为参数调用DllMain,那么delphi最后会以该参数调用DllProc,因此我们只要实现一个新的TDllProc实现ThisDllProc,并将DllProc指向ThisDllProc,而在ThisDllProc中,当收到DLL_THREAD_ATTACH时把IsMultiThread设置为True即可。实现代码如下:varOldDllProc: TDLLPprocedure ThisDllProc(Reason: Integer);beginif Reason = DLL_THREAD_ATTACH thenIsMultiThread := Tif Assigned(OldDllProc) thenOldDllProc(Reason);beginOldDllProc := DllPDllProc := ThisDllPThisDllProc(DLL_PROCESS_ATTACH);六、总结本文主要讨论了下面几个问题:1、为什么要使用FastMM2、跨模块传递动态内存变量会出现什么问题,原因是什么3、delphi的内存管理和内存管理器是怎么回事4、为什么要共享内存管理器,delphi和FastMM分别是如何实现内存管理器共享的5、多线程环境下,内存管理器如何实现同步6、多线程环境下,如何跨模块设置IsMultiThread变量以保证内存管理器会进行同步要正确使用FastMM,在模块开发的时候需要完成以下工作:1、打开编译选项{$define ShareMM}、{$define ShareMMIfLibrary}、{$define AttemptToUseSharedMM}2、将FastMM(4).pas作为每个工程文件的第一个uses单元 3、如果是dll,需要处理以DLL_THREAD_ATTACH为参数的DllMain调用,设置IsMultiThread为True&1&& FastMM是开源项目, 从&&&下载最新版2&& 文件夹Replacement BorlndMM DLL/Precompiled/for Delphi IDE/Performance/BorlndMM.dll,替换掉Delphi/Bin下的相应文件就可以完成对IDE的提速3& Enviroment-&Library-&Directories-&&Library Path&添加FassMM路径,我放在Delphi安装目录下,直接设置为$(DELPHI)/FastMM4& 在你的项目文件中,Project-&View Source打开后,uses 后第一个添加FastMM4单元5& 编译运行你的程序,如果有Memory leak,在关闭程序时会有一个提示对话框.对话框也是可以关闭的打开FastMM4Options.inc文件。在文件的末尾添加如下代码:{$define Release}{$ifdef Release}& {Specify the options you use for release versions below}& {$undef FullDebugMode}& {$undef CheckHeapForCorruption}& {$define ASMVersion}& {$undef EnableMemoryLeakReporting}& {$undef UseOutputDebugString}& {$undef LogErrorsToFile}& {$undef LogMemoryLeakDetailToFile}{$else}& {Specify the options you use for debugging below}& {$define FullDebugMode}& {$define EnableMemoryLeakReporting}{$define UseOutputDebugString}{$endif}重新打开把上面第一行条件编译定义选项{$define Release} 注释掉就可以了如 //{$define Release} 即可.
07-2110-2808-2008-0308-0207-2507-2504-2403-2602-25
阅读本文后您有什么感想? 已有23人给出评价!
名称大小下载delphi内存释放应该怎么操作?_电脑网络问题_土巴兔问吧
delphi内存释放应该怎么操作?
报价结果将发送到您的手机
装修顾问-馨馨
4年行业经验,24h可咨询
10秒闪电通过好友
您的装修预算约
*装修管家将回电您,免费提供装修咨询服务
*因材料品牌及工程量不同,具体报价以量房实测为准
装修顾问 -馨馨
(四年装修行业经验)
微信扫一扫
delphi内存释放应该怎么操作?
提问者:孙朗丽|
时间: 11:42:38
已有3条答案
回答数:2464|被采纳数:0
所有回答:&2464
delphi释放内存操作:在循环导入数据时,内存溢出,在循环里每次清一下内存,或者设置循环1000次清一下内存,powerbuilder清内存的函数写法,在每次循环的最后,加上GarbageCollect(),强制垃圾回收,这样内存占用率就低了。希望我的回答能够帮助到您。
回答数:177|被采纳数:0
所有回答:&177
1.内存分为三个区域:全局变量区,栈区,堆区 && && &&全局变量区:专门存放全局变量 && && &&栈区:分配在栈上的变量可被栈管理器自动释放 && && &&堆区:堆上的变量内存必须人工去释放
2.指针类变量 && &&指针类的变量在声明为全局变量时被初始化为空值,在声明为局部变量时则初始化为一个随机数,对于指针类型一般 &&,要使用一些代码来完成内存分配
3 &&动态分配内存的函数和过程
&& && procedure GetMem(Var P: P Size: Integer);
&& && &&分配大小为Size字节的内存块,并让P指向它 &&
&& && && &&procedure AllocMem(Size: Cardinal):P
&& && &&分配大小为Size字节的内存块并始初化为零,并返回地址指针
&& && &&procedure ReallocMem(Var P: P Size: Integer);
&& && &&在途中更改先前用GetMem或者AllocMem分配的内存大小
&& && &&procedure FreeMem(Var P: Pointer)
&& && &&使用GetMem和AllocMem分配的内存都应该用FreeMem释放
&& && &&Procedure New(Var P: Pointer);
&& && &&用New分配的内存大小由参数P的数据类型确定,因此,不要使用它给无类型指针变量分配内存
&& && &&Procedure Dispose(Var P: Pointer);
&& && &&释放该内存块使用dispose
希望我的回答对你有所帮助
回答数:10678|被采纳数:12
所有回答:&10678
<p class="ask_one_p edit_、之所以不出错,是因为操作系统目前还没有使用这块内存,释放和重新利用是两个概念。
2、ZeroMemory与FreeMem根本就是两个不同功能的函数,一个清空内存(填充0),一个释放内存(把资源还给操作系统),代替个球?
3、CopyMemory仅仅是复制了9个字符,字符串必须以0结尾,你也没有置零。把 &&GetMem(A^.pac, &&100)改成A^.pac &&:= &&AllocMem(100)才对。
已有 3 个回答
已有 3 个回答
已有 3 个回答
已有 3 个回答
已有 3 个回答
位业主已在问吧找到答案
北欧简约、复古美式、大气欧式、清新地中海风,总有一款适合你!
一万套装修案例
下载土巴兔APP
中国装修网谈Delphi中LocalObject内存分配的优化
我的图书馆
谈Delphi中LocalObject内存分配的优化
&&& 谈Delphi中LocalObject内存分配的优化&&& 众所周知,Delphi中,所有的object都是开辟在堆里面的。实际上,Delphi是不存在C++中的LocalObject的概念的。但是LocalObject内存分配的高效实在有目共睹,本文主要就是讲如何把Delphi的Object申请到Stack上面去。机制其实很简单,大部分的情况都可以用这种方式来优化(如果有优化必要的话)。小弟在此抛砖引玉,愿与达者交流切磋。&&& &&& 对于LocalObject的使用,基本就是一个临时对象的作用, Delphi里面和C++里面的写法分别是这个样子的。&&& &&& //// sample.cpp&&& /// ......&&& int test()&&& {&&& &&& SampleC&&& &&& return a.execute(1, 2);&&& &&& &&& }&&& &&& /// sample.pas&&& function test(): I&&& var&&& &&& a: TSampleC&&& begin&&& &&& a := TSampleClass.Create();&&& &&& try&&& &&& &&& result := a.execute(1, 2);&&& &&& finally&&& &&& &&& a.Free();&&& &&&&&&&&& 单以形式而论,这两个函数的几乎是一模一样。可是他们的效率,却有着很大的差别,有心人可以自行测试一下,stack object的效率明显比heap object的效率高不少。尤其是,这样的小函数被调用次数非常多的时候,比如我在实际工程里遇到的一个几何函数里面的相交判断,被调用的次数达百万的级别,且有些地方直接影响到界面的刷新,效率要求比较高些。出于能快些就快些的极端主义思想,这里的内存分配已经不容小觑了,所以需要懂些手脚,令人庆幸的是,动这个手脚还一点也不麻烦。&&& 我的方法很简单,就是把内存分配到系统栈上去。我们知道,Delphi里面有一个record结构(对应于c/c++里面的struct),虽然record并不如c里面的好用,可是他的内存是分配在栈上这是一样的。(其实还可以利用静态数组,一样可以做到这一点,本质上是一样的。我这里还是采用了一个record, 这样应该好理解一些。)&&& 我先把修改过的代码贴上,接下来再解释原理。&&& /// sample.pas&&& type&&& &&& TSampleClass = class(TObject)&&& &&& public&&& &&& &&& procedure Finalize();&&& //// 负责释放引用类型的成员变量&&& &&&&&& &&& &&& &&& TSampleClassRec = record&&& &&& &&& fcontent: array [0..const_TSampleClass_Size-1]&&& &&&&&& &&& /// ....&&& function test(): I&&& var&&& &&& aRec: TSampleClassR &&& &&& /// 一个大小和TSampleClass一样的record&&& &&& a: TSampleC&&& begin&&& &&& a := TSampleClass(TSampleClass.InitInstance(@aRec));&& //// 注意,这里不同了&&& &&& a.Create();&&& &&& try&&& &&& &&& result := a.execute(1, 2);&&& &&& finally&&& &&& &&& Finalize();&&& &&& &&& &&& &&& ////&&& &&& &&& a.CleanupInstance();&&& &&& //// 注意,这里不同了&&& &&&&&&&&& &&& 下面,我们来解释原因。&&& 在Delphi里面,object的创建,是通过TObject.Create来完成的。下面我们来看看TObject.Create里面到底做了些什么。&&& 我们可以到System.pas单元里去看看constructor TObject.Create这个函数,会比较惊讶的发现,空的,什么都没有!!!什么都没做!!!不错,确实什么都没有,但绝对不是什么都没做,我们说还是做了不少事情的,人家是田螺姑娘的作风,喜欢偷偷的干活,不愿被别人看到。那么,到底是怎么完成的呢?这里可以建议一本书,李维先生的《inside VCL》比较详细的阐述了这个过程。不过,不喜欢看书的兄弟,也没有关系,可以看我写的,大约100来字,倒也可以把过程讲明白(喜欢深刻的兄弟还是建议看一下,实际上是调用了System.pas里面的_ClassCreate函数,一堆汇编,暂时先供起来)。&&& 我们可以看Delphi提供的一个实例,他把整个过程,分割开来了,堪称模板型的代码。请看VCL源码里面Forms.pas的procedure TApplication.CreateForm(InstanceClass: TComponentC var Reference);函数。&&& procedure TApplication.CreateForm(InstanceClass: TComponentC var Reference);&&& var&&& & Instance: TC&&& begin&&& & Instance := TComponent(InstanceClass.NewInstance);&&& &&& //// 首先,创建实例,分配内存,VMT等&&& & TComponent(Reference) := I&&& & try&&& &&& Instance.Create(Self);&&& &&& &&& &&& &&& &&& &&& &&& &&& //// 现在,调用初始化函数,初始化userdata&&& & except&&& &&& TComponent(Reference) :=&&& &&&&&& &&&& & if (FMainForm = nil) and (Instance is TForm) then&&& & begin&&& &&& TForm(Instance).HandleN&&& &&& FMainForm := TForm(Instance);&&& &&&&&&& &&& 不错,TObject.Create 实际就是这两个步骤完成的。首先NewInstance, 然后Create。这里,大家会发现一个疑问,这里是通过一个实例来调用Create,而不是正常的通过Class来调用。这两种方式的区别就在于,是否需要第一个NewInstance过程,开辟内存。这点可以在function _ClassCreate(AClass: TC Alloc: Boolean): TO看出来,参数Alloc: Boolean和JL&&&&& @@noAlloc这一行就是根据调用方式的不同,是否需要分配内存的两条路径。&&& 到这里,我们的任务就是,看看NewInstance作了什么。发现以下的结果&&& class function TObject.NewInstance: TO&&& begin&&& & Result := InitInstance(_GetMem(InstanceSize));&&&&&& 原来是通过_GetMem来实现的,那么问题就简单了,什么神秘的面向对象的一大套的复杂招式,在独孤九剑下面,看他的本质,还是一块内存,而且是毫无二致,普通得不能再普通的内存块而已。&&& 到此为止,我们的这个LocalObject的机制已经毫无遮掩了,完完全全,彻彻底底,赤裸裸展现在眼前了。我的任务也完成了,充当了一回外科医生的角色,应该说“报告队长,我已经切好了,请验收”“好啊,可以缝回去了”“收到”。&&& 要缝回去,还需要注意几个地方,要不然缝错了,搞得五官不正,也太对不起医生的称号了,鲁迅先生对庸医深恶痛绝,我们追随先贤遗烈,万万不可轻易启衅。&&& 第一个问题,TSampleClass的实际大小如何获得。这个很不幸,我们无法用sizeof来得到一个object的大小。有两种办法,一个是正道的方式,不过很痛苦,麻烦;一个是邪一点,不过很有效,最重要的是很简单。正道的方式,自然是直接分析数据,看实际占多少内存,但这个需要很深的功力才能做到,而且即使功力真的很深,难免犯错,容易走火入魔,稍不留意,前功尽弃。另一个方式,本人比较推荐的方式,把程序运行以下,进调试界面,或者写message,调用instancesize函数,看一下那个数字就可以了。任务完成。不过,作为一个有良心的医生,虽然不能根治毛病,最好给一个防御措施。强烈建议写上, Assert(TSampleClass.InstanceSize() = SizeOf(TSampleClassRec));&&& 第二个问题,千万要注意,绝对不能对这种Stack Object调用Free, 或者 destroy什么的不需要了。但是,这也会带来一个问题,对于成员的释放就有问题了. 象Integer, Char, Double等是没有关系,可以直接不理,但是对于string, 动态数组等,最好还是释放一下先。于是,需要一个Finalize()函数,负责释放引用类型的成员,在finally段里面记得调用。&&& 第三个问题,CleanupInstance.还是需要调用的,这叫做断后。大丈夫光明磊落,来得清楚,去得明白。&&& 另外,这种方式的优化,在于调用次数比较多的时候,以及分配内存的时间和算法本身差别不大的时候,效果比较明显。之所以有这样的效果,就是靠了stack上面分配内存只需修改esp值即可,都不用访问内存。而getmem,我们说至少通过一个系统的调用。windows下是怎么实现的倒是不清楚,当初在学操作系统的时候,老师讲过一个buddy的内存分配算法,相当的巧妙,也相当的繁复。与栈上分配花的时间不可同日而语。因为他是一种通用的内存分配算法,需要考虑内存的释放,回收,还要考虑一个内存碎片的处理等。所以,基于这种想法,我们实际上除了对LocalObject使用本文描述的机制来优化之外,对于那些确实需要创建在堆里的object,专门写一个内存池的分配方式。比如一开始就开辟一个TSampleClassRec的大数组,每次创建,扔出来一份,释放则扔回去一份。这个机制,比通用的分配机制在碎片的处理上,效率可以高很多。我们可以另外探讨一下。(对此,effectice c++一书中, 有一点提及, 但也足够了.高手就是高手,几句话就能把本质讲清楚, 使我颇有感悟,才有了这篇文字, 在此略表敬意.)&&& &&& &&& 终于写完了,医生需要休息一下了。希望这个小小技巧可以为你解决一些问题,便是本人无尚荣幸了。抛砖引玉,多多分享。关于一些实例的临时对象的效率优化,注意这里临时对象的概念。比如:在我们的CalcFunc里面有些这种形式的函数,我例举一个如下class function TCSeg2D.IntersectWith(const Line1, Line2: TWXL var Pt1,& Pt2: TWP ATol: Double): TIntRvar& Seg1, Seg2: TCSeg2D;& begin& Seg1 := TCSeg2D.Create(Line1);& try&&& Seg2 := TCSeg2D.Create(Line2);&&& try&&&&& Result := Seg1.IntersectWith(Seg2, Pt1, Pt2, ATol);&&& finally&&&&& Seg2.F&&&& finally&&& Seg1.F&在我们的实现逻辑里面,可以看到seg1, seg2,除了参与了一下小小的计算,并没有其他的作用,本地创建,本地释放了。我们把这些东西叫做临时对象。(他实际起到的作用也是一个临时存些数据)但是,这里实际上有一份不小的开销,他必须在堆上创建两个实例出来,需要通过 _GetMem 来得到他所需要的内存。这个开销实际上很大,尤其是对于经常调用的函数,简直是随着调用次数级数上升.所以,我的想法是把这些内存的开辟,放到栈上面去完成。我试了一下, 这样的优化效率还是比较可观的。比如有这么一个类& TSmall = class(TObject)& private&&& FContext: I& public&&& constructor C&&& destructor D&&& procedure R&&& function sum(I, J: Integer): I&& & 里面放了一个Integer, 模拟数据, 实际上就算包括需函数表, 理论上讲是没有问题的.& & sum函数就是我们的业务函数.& & 根据他的InstanceSize 看到实际大小是 8个字节.& & 所以我又定义了一个 8字节的record. 作为栈上申请内存用。& & TSmallRec = record&&& FContext: array [0..1] of I&& & & 需要注意的地方, 我们再也不能调用destroy函数了,因为它为会去做释放内存的操作,但是我们的内存将会创建在栈上,& & 所以函数会自己退栈。所以还是需要释放出了内存之外的其他资源,所以定义了一个release 函数。& & ///////////////////////////////////////& 现在看实际上的使用方式//// 这个是原来的用法procedure TTestSmall.DoObegin& with TSmall.Create() do& try&&& sum(1, 3)& finally&&& Free();&//// 新的用法procedure TTestSmall.DoNvar& rSmall: TSmallR& oSmall: TSbegin& oSmall := TSmall(TSmall.InitInstance(@rSmall));& with oSmall.Create() do& try&&& sum(1, 3);& finally&&& cleanupinstance();&我对他们分别作了1000次的调用,结果是old:&& time used(run 1000 times): 1092new:&& time used(run 1000 times): 454/////我做了一个测试,得出一个结论:object越大,效果越好。业务越简单,效果越好。可以参考一下内存管理,我基本考虑的是buddy算法的机制。
馆藏&22275
TA的最新馆藏

我要回帖

更多关于 delphi 申请内存 的文章

 

随机推荐