Delphi 的記憶體操作函式(2): 給陣列指標分配記憶體
靜態陣列, 在宣告時就分配好記憶體了, 譬如:
var arr1: array[0..255] of Char; arr2: array[0..255] of Integer; begin ShowMessageFmt('陣列大小分別是: %d、%d', [SizeOf(arr1), SizeOf(arr2)]); {陣列大小分別是: 512、1024} end;
對靜態陣列指標, 雖然在宣告之處並沒有分配記憶體, 但這個指標應該分配多少記憶體是有定數的.
這種情況, 我們應該用 New 和 Dispose 來分配與釋放記憶體. 譬如:
type TArr1 = array[0..255] of Char; TArr2 = array[0..255] of Integer; var arr1: ^TArr1; arr2: ^TArr2; begin New(arr1); New(arr2); arr1^ := '萬一的 Delphi 部落格'; ShowMessageFmt('%s%s', [arr1^[0], arr1^[1]]); {萬一} // ShowMessageFmt('%s%s', [arr1[0], arr1[1]]); {這樣也可以} arr2[Low(arr2^)] := Low(Integer); {第一個元素賦最小值} arr2[High(arr2^)] := MaxInt; {第一個元素賦最大值}ShowMessageFmt('%d, %d', [arr2[0], arr2[255]]); {-2147483648, 2147483647} Dispose(arr1); Dispose(arr2); end; //變通一下, 再做一遍這個例子: type TArr1 = array[0..255] of Char; TArr2 = array[0..255] of Integer; PArr1 = ^TArr1; PArr2 = ^TArr2; var arr1: PArr1; arr2: PArr2; begin New(arr1); New(arr2); arr1^ := '萬一的 Delphi 部落格'; ShowMessageFmt('%s%s', [arr1[0], arr1[1]]); arr2[Low(arr2^)] := Low(Integer); arr2[High(arr2^)] := MaxInt; ShowMessageFmt('%d, %d', [arr2[0], arr2[255]]); {-2147483648, 2147483647} Dispose(arr1); Dispose(arr2); end;
給已知大小的指標分配記憶體應該用 New, 上面的例子是關於靜態陣列指標的, 後面要提到的結構體(記錄)的指標也是如此.
New 的本質也函式呼叫 GetMem, 但不需要我們指定大小了.
但這對動態陣列就不合適了, 不過給動態陣列分配記憶體 SetLength 應該足夠了, 譬如:
var arr: array of Integer; begin SetLength(arr, 3); arr[0] := Random(100); arr[1] := Random(100); arr[2] := Random(100); ShowMessageFmt('%d,%d,%d', [arr[0],arr[1],arr[2]]); {0,3,86} end;
那怎麼給動態陣列的指標分配記憶體呢? 其實動態陣列變數本身就是個指標, 就不要繞來繞去再給它弄指標了.
不過有一個理念還是滿重要的, 那就是我們可以把一個無型別指標轉換為動態陣列型別, 譬如:
type TArr = array of Integer; var p: Pointer; begin GetMem(p, 3 * SizeOf(Integer)); {分配能容納 3 個 Integer 的空間} {這和 3 個元素的 TArr 的大小是一樣的, 但使用時需要進行型別轉換} TArr(p)[0] := Random(100); TArr(p)[1] := Random(100); TArr(p)[2] := Random(100); ShowMessageFmt('%d,%d,%d', [TArr(p)[0], TArr(p)[1], TArr(p)[2]]); {0,3,86} FreeMem(p); end;
這裡用到了 GetMem 和 FreeMem, 對分配無型別指標這是比較常用的; 對其他型別的指標它可以, 但不見得是最好的方案, 譬如:
//獲取視窗標題(顯然不如用前面說過的 StrAlloc 更好) var p: Pointer; begin GetMem(p, 256); GetWindowText(Handle, p, 256); ShowMessage(PChar(p)); {Form1} FreeMem(p); end;
應該提倡用 GetMemory 和 FreeMemory 代替 GetMem、FreeMem, 譬如:
var p: Pointer; begin p := GetMemory(256); GetWindowText(Handle, p, 256); ShowMessage(PChar(p)); {Form1} FreeMemory(p); end;
先總結下:
New 是給已知大小的指標分配記憶體;
GetMem 主要是給無型別指標分配記憶體;
儘量使用 GetMemory 來代替 GetMem.
還有個 AllocMem 和它們又有什麼區別呢?
AllocMem 分配記憶體後會同時初始化(為空), GetMem 則不會, 先驗證下:
var p1,p2: Pointer; begin p1 := AllocMem(256); ShowMessage(PChar(p1)); {這裡會顯示為空} FreeMemory(p1); p2 := GetMemory(256); ShowMessage(PChar(p2)); {這裡會顯示一些垃圾資料, 內容取決與在分配以前該地址的內容} FreeMemory(p2); end;
關於 FreeMemory 與 FreeMem 的區別:
1、FreeMemory 會檢查是否為 nil 再 FreeMem, 這有點類似: Free 與 Destroy;
2、FreeMem 還有個預設引數可以指定要釋放的記憶體大小, 不指定就全部釋放(沒必要只釋放一部分吧);
3、New 對應的 Dispose 也可以用 FreeMem 或 FreeMemory 代替.
儘量使用 FreeMemory 來釋放 GetMem、GetMemory、AllocMem、ReallocMem、ReallocMemory 分配的記憶體.
ReallocMem、ReallocMemory 是在已分配的記憶體的基礎上重新分配記憶體, 它倆差不多 ReallocMemory 比 ReallocMem 多一個 nil 判斷, 儘量使用 ReallocMemory 吧. 譬如:
type TArr = array[0..MaxListSize] of Char; PArr = ^TArr; var arr: PArr; i: Integer; begin arr := GetMemory(5); for i := 0 to 4 do arr[i] := Chr(65+i); ShowMessage(PChar(arr)); {ABCDE} arr := ReallocMemory(arr, 26); ShowMessage(PChar(arr)); {ABCDE} for i := 0 to 25 do arr[i] := Chr(65+i); ShowMessage(PChar(arr)); {ABCDEFGHIJKLMNOPQRSTUVWXYZ} end;
注意上面這個例子中 TArr 型別, 它被定義成一個足夠大的陣列; 這種陣列留出了足夠的可能性, 但一般不會全部用到.
我們一般只使用這種陣列的指標, 否則一初始化將會記憶體不足而當機.
即便是使用其指標, 也不能用 New 一次行初始化; 應該用 GetMem、GetMemory、AllocMem、ReallocMem、ReallocMemory 等用多少申請多少.
需要注意的是, 重新分配記憶體也可能是越分越少; 如果越分越大應該可以保證以前資料的存在.
這在 VCL 中 TList 類用到的理念.
如果你在心裡上接受不了那麼大一個數組(其實沒事, 一個指標才多大? 我們只使用其指標), 也可以這樣:
type TArr = array[0..0] of Char; PArr = ^TArr; var arr: PArr; i: Integer; begin arr := GetMemory(5); for i := 0 to 4 do arr[i] := Chr(65+i); ShowMessage(PChar(arr)); {ABCDE} arr := ReallocMemory(arr, 26); ShowMessage(PChar(arr)); {ABCDE} for i := 0 to 25 do arr[i] := Chr(65+i); ShowMessage(PChar(arr)); {ABCDEFGHIJKLMNOPQRSTUVWXYZ} end;
這好像又讓人費解, 只有一個元素的陣列能幹什麼?
應該這樣理解: 僅僅這一個元素就足夠指示資料的起始點和資料元素的大小和規律了.
另外的 SysGetMem、SysFreeMem、SysAllocMem、SysReallocMem 四個函式, 應該是上面這些函式的底層實現, 在使用 Delphi 預設記憶體管理器的情況下, 我們還是不要直接使用它們.