轉載自:http://www.cnblogs.com/qiusl/p/4028437.html?utm_source=tuicool
我估摸着內存分配+釋放是個基礎的函數,有些人可能沒注意此類函數或細究,但我覺得還是弄明白好。
介紹下面內存函數之前,先說一下MM的一些過程,如不關心可以忽略:
TMemoryManger = record GetMem: function(Size: Integer): Pointer; FreeMem: function(P: Pointer): Integer; ReallocMem: function(P: Pointer; Size: Integer): Pointer; end; var MemoryManger: TMemoryManager = ( GetMem: SysGetMem; FreeMem: SysFreeMem; ReallocMem: SysReallocMem);
以上時D7版本的MM函數,其中變量MemoryManger我稱為MM函數,請注意。
D2005-D2007以上版本(不確定是哪個版本),MM函數多了AllocMem以及RegisterLeak/UnRegisterLeak,與本文無關,就不多說了。
第三方MM接管的就是這MM的幾個函數,達到外掛目的,而Sys打頭的SysGetMem,SysFreeMem,,SysReallocMem則為本身系統自帶的MM處理。
New/Dispose
此兩函數,估計學習Delphi/Pascal就知道:為record/object此類數據進行分配和釋放內存塊。
然后分配與釋放是調用的是GetMem/FreeMem函數,與GetMem/FreeMem不同之處是:New()在GetMem后,進行了initialize(x)操作,即對record/object的數據進行初始化的操作。
initialize函數,在system單元,該函數說白了,即對record.object里面含有string、interface、dync array、variant、record、array的字段,進行初始化為0(清空)。
這一步很重要,因為GetMem返回的內存塊可能重復使用過,表示有值。
有值的情況下,再重復賦值,就表示舊地址對應的數據要先清空,清空隨即地址的數據?AV就會出現了。。。
(不要想着,再GetMem后,進行每個字段初始化,容易出錯的就是這個,在有以上字段的情況下,如果需要手動初始化,必須用fillchar,原因如上)
與之相反的Dispose()亦同,反操作,進行清空:finalize(x),之后,再進行FreeMem,以保證record/object中,string、interface、dync array字段,不會因為直接調用FreeMem而泄露(Leak)。
總結是:
1) New==>GetMem(p, sizeof(TDataType)) + Initialize(p^)==>AllocMem(sizeof(TDataType);
它與AllocMem的區別是:initialize(x)不會對每個字節清0,只針對某些字段清0.
Dispose==Finalize(p^)+FreeMem(p);
沒有可替代的函數,也不能少finalize(p^)這步操作,否則會有leak。
2) record/object 的指針類型,最好使用此對函數(New/Dispose)進行分配與釋放。當然你也可以去自維護record/object里面的字段生存期。
但是New()只能獲取對象的單個實體的內存大小,無法取得連續的存放多個對象的內存塊,所以New適合分配record/object的內存,但是不適合動態分配數組內存。在這個方面GetMem就有優勢
3) 如果調用system.Initialize或Finalize,出現提示:
[Hint] Unit1.pas(43): Expression needs no Initialize/Finalize
表示record/object 里面的字段,沒有包含string,interface,dync array,variant,record,array,也就表示不需要調用Initialize或 Finalize進行操作
4) 多說一句:每個warn/hint都有其作用,請勿忽略,說不定小bug就在其中,請關注它們並干掉它們
GetMem/FreeMem
GetMem/FreeMem是MM的分配與釋放內存塊函數,多說一些與之相關的:此兩函數,會因為分配或釋放失敗而拋出異常。
而MM對應的標准分配與釋放函數是以返回值形式進行處理的,即失敗了,只會返回空值(nil)或非0,而不是異常。
也就是說Get/FreeMem是針對MM的標准函數進行了異常封裝。
異常信息:
GetMem fail =>Out of Memory:分配失敗,一般只會是進程的可用內存分配完畢,通常在內存泄露的情況下才會發生。
FreeMem fail=>Invalid pointer operation:兩次FreeMem同地址,第二次就有這個invalid pointer異常了
GetMemory/FreeMemory
Get/FreeMemory與Get/FreeMem基本相同,唯一的不同在於,它直接以MM的對應函數的返回值作為返回,而不進行異常處理。
即:GetMem調用MM.GetMem返回為nil,則有異常,而GetMemory則直接返回nil,交給調用者處理。
FreeMem調用MM.FreeMem返回非0(錯誤釋放),則異常,而FreeMemory則直接返回0或非0,給調用者處理。
這點非常有用,在寫程序的時候,可以減少異常,或者在Get/Fee出現錯誤時,寫句assert(...),讓程序中斷下來,檢查並調試。
SysGetMem/SysFreeMem
SysGetMem/SysFreeMem與GetMemory/FreeMemory基本相同,區別在於,它直接調用MM的實現函數,而不是經過MM的管理器指針再行跳轉。
即:SysGet/SysFreeMem,它使用的是系統自帶的MM分配釋放函數,當第三方MM加入后,以上三隊函數都會由第三方MM接管,但SysGet/SysFreeMem它還是調用的本系統自帶的MM函數處理,與第三方的MM無關。
其他
其他還有些Delphi單元的分配釋放函數,不過基本是從以上四個函數擴展出來的,就不說明了。
當然也有從API擴展出來的分配+釋放函數,則不在此列,它與Delphi系統的MM擴展無關。
總結
New+Dispose與GetMem+FreeMem,是基於VCL異常機制保護的分配+釋放函數
GetMemory+FreeMemory與SysGetMem+SysFreeMem是由調用者自行控制返回,來決定是否返回異常或錯誤處理。
但是New()只能獲取對象的單個實體的內存大小,無法取得連續的存放多個對象的內存塊,所以New適合分配record/object的內存,但是不適合動態分配數組內存
最后參考一篇比較好的博客,進行一些補充
參考博客:delphi dispose釋放內存的方法 New 和 GetMem 的區別
定義一個record 類型,經過多次new dispose后,從windows任務管理器看,占用的內存比啟動時大了很多,似乎越來越大
設置 ReportMemoryLeaksOnShutdown := true; 再運行,仍然沒有提示 memory leak。
其實就是dispose 本身的原因。
delphi設計的 dispose 釋放內存時,只是標記這部分內存可以再用來被 new 等函數分配,並不是把從系統申請到的內存歸還給操作系統,只在程序結束時,才全部釋放給操作系統。
比如 new 申請 15 個記錄(sizeof=64字節) 的空間,然后 dispose 釋放。再使用 new 申請 10個,此時這 10 個就不再請求系統了,直接從剛才的 15個 (此時已經空閑) 中分10 個出來。只有在占用的空閑內存不夠使用時,才請求操作系統分配內存(剩余部分)。
若前一次15個空間地址如左列,釋放后,下一次10個空間的地址如右列,即從前次分配的最后一個地址開始,按前次的順序,倒過來分配10個。
|
|
|
如果操作一個 record 指針中的字符串變量,會不會丟失 string 的內存空間,造成內存泄漏?
結果是:使用 New() 分配的內存,會自動初始化 record 的內容,並且在 Dispose 時自動清除所有已分配的內存,包括 string 或其他動態數組的內存。GetMem/FreeMem 沒有這個性質。事實上,New() 中調用了 GetMem,並且執行了一些初始化的操作。
代碼如下:
type PMyRecord = ^TMyRecord; TMyRecord = record I: Integer; S: string; V: Variant; end; {;$DEFINE NEW} procedure TForm1.Button1Click(Sender: TObject); var R: PMyRecord; I: Integer; begin for I := 1 to 1024 do begin {$IFDEF NEW} New(R); // 正確將 R.S 初始化 SetLength(R.S, $FFFF); Dispose(R); // 正確釋放 R.S 內存空間 {$ELSE} GetMem(R, SizeOf(TMyRecord)); R.S := ''; // 出錯 SetLength(R.S, $FFFF); FreeMem(R); {$ENDIF} end; end;
===================================================
GetMem 只負責分配空間,不會負責清空剛分配的空間,如果需要分配來就清空的空間可以用 AllocMem,AllocMem = GetMem + FillChar
===================================================
哦,我上面犯錯誤了,以為 S = 0 時也會出錯,所以沒有 FillChar,其實不會。
這樣就可以正確測出結果了:
(將 $DEFINE NEW 前面的 ; 去掉,可以從任務管理器中查看二種方法的內存占用)
type PMyRecord = ^TMyRecord; TMyRecord = record I: Integer; S: string; V: Variant; end; {;$DEFINE NEW} procedure TForm1.Button1Click(Sender: TObject); var R: PMyRecord; I: Integer; begin for I := 1 to 1024 do begin {$IFDEF NEW} New(R); // 正確將 R.S 初始化 SetLength(R.S, $FFFF); Dispose(R); // 正確釋放 R.S 內存空間 {$ELSE} GetMem(R, SizeOf(TMyRecord)); FillChar(R^, SizeOf(TMyRecord), #0); SetLength(R.S, $FFFF); FreeMem(R); // 不會釋放 R.S 內存空間 !! {$ENDIF} end; end;
===================================================
是的,FreeMem 不會釋放其中的生存期自動管理的內容,因為在 FreeMem看來,那些都是一致的二進制數據,沒有任何意義可言。不過可以通過一個Finalize 調用(或者 FinalizeRecord)解決該問題 我估計 Dispose 就直接或間接調用了 Finalize
===================================================
呵呵,不用估計,幫助中明確地說明了。
In Delphi code, FreeMem destroys the variable referenced by P and returns its memory to the heap. If P does not point to memory in the heap, a runtime error occurs. If P points to a structure that includes long strings, variants, dynamic arrays, or interfaces, call Finalize before calling Freemem.
......
Note: It is preferable to use the New and Dispose procedures rather than GetMem and FreeMem. When using New and Dispose, there is no need to explicitly call Finalize.
===================================================
guttier wrote:
呵呵,不用估計,幫助中明確地說明了。
In Delphi code, FreeMem destroys the variable referenced by P and returns its memory to the heap. If P does not point to memory in the heap, a runtime error occurs. If P points to a structure that includes long strings, variants, dynamic arrays, or interfaces, call Finalize before calling Freemem.
......
Note: It is preferable to use the New and Dispose procedures rather than GetMem and FreeMem. When using New and Dispose, there is no need to explicitly call Finalize.
這段英文我翻譯一下。
“在DELPHI代碼中,FreeMem根據變量所引用的指針釋放內存,並將內存歸還給堆。如果指針不是指向堆中的內存地址,將發生一個運行時錯誤。如果指針所指向的是一個數據結構,且其中包含有長字符串、Variants、動態數組、或接口,則在使用用FreeMem之前須調用Finalize ”
"注意:使用New 和 Dispose 過程要強於使用GetMem與FreeMem。但我們使用New和Dispose的時候,不需要顯示的調用Finalize "
翻譯完了。肯定有不准確的地方。不過我有一個問題,內存分配既然new和Dispose要比GetMem與FreeMem容易使用,那么還有沒有必要使用GetMem、FreeMem,在什么情況下使用它們?
===================================================
內存分配既然new和Dispose要比GetMem與FreeMem容易使用
容易使用”通常只能是一個相對的概念,在這里,我們討論指向結構體的指針,在這種情況下,New/Dispose 通常是易用的。但是它們是有局限的,就是那個指針指向的空間的大小必須能夠在編譯期間確定,它們才知道需要分配多大的空間。對於指向結構體的指針,這個值就是結構體的大小,這當然是確定的,所以能夠使用它們,並且能夠帶來便利。
可是還有一些情況,例如你只有一個 Pointer 類型,這是無類型指針,你把它傳給 New,編譯器就不知道它指向的是什么內容,也就不知道它指向的空間有多大,也就不知道需要分配多少空間,就根本不能用,更不用說易用了。
還有沒有必要使用GetMem、FreeMem,在什么情況下使用它們?
當然有了,如前面提到的,相對來說,New/Dispose 操作更為高層一些,我們通常用它們來操作指向結構體的指針,即是說指針指向的內容是編譯期間就已知的數據結構。而當我們需要更加低級的去操作一些內存空間的時候,比如你要自己處理字符串的時候,你的指針指向的就是只有你自己才知道或者說是你自己去進行理解的內存空間,沒有編譯期間的明確的數據結構與之對應。這個時候,就要用到 GetMem/FreeMem 了。
所以說,New/Dispose 的局限性實際上是很大的,或者說適用范圍是很小的,而 GetMem/FreeMem 給了我們充分的自由,試用范圍更廣。當然,具體選用哪個,還要看實際情況而定。