Delphi 的記憶體操作函數1-2: 給數組指標分配記憶體 2009-05-13 13:35
分類:Delphi 字型大小: 大大 中中 小小
GetMem
AllocMem
ReallocMem
FreeMem
GetMemory
ReallocMemory
FreeMemory
New
Dispose
NewStr
DisposeStr
StrNew
StrAlloc
StrDispose
GlobalAllocPtr
GlobalFreePtr
WideStrAlloc
AnsiStrAlloc
StrDispose
Move
MoveMemory
CopyMemory
ZeroMemory
FillMemory
FillChar
StrBufSize
給字元指標(PChar、PWideChar、PAnsiChar)分配記憶體, 最佳選擇是: StrAlloc.
StrAlloc 雖然最終也是調用了 GetMem, 但 StrAlloc 會在指標前面添加 Delphi 需要的 4 個管理位元組(記錄長度).
StrAlloc 分配的記憶體, 用 StrDispose 釋放, 用 StrBufSize 擷取大小.
用 FreeMem 釋放可以嗎? 這樣會少釋放 4 個位元組.
這種類型的指標一般用於 API 函數的參數, 譬如擷取視窗標題:
var
p: PChar;
begin
p := StrAlloc(256);
GetWindowText(Handle, p, StrBufSize(p));
ShowMessage(p); {Form1}
StrDispose(p);
end;
StrAlloc 根據不同的參數(PWideChar、PAnsiChar)分別重載調用了 WideStrAlloc、AnsiStrAlloc, 所以我們也可以直接使用這兩個函數(這也需要用 StrDispose 釋放), 不過使用它們的必要性不大; 用 StrAlloc 指定好參數類型即可.
給字元指標分配記憶體其他方法也挺方便, 譬如:
//擷取 WINDOWS 所在目錄
var
buf: array[0..MAX_PATH] of Char;
begin
GetWindowsDirectory(buf, Length(buf));
ShowMessage(buf); {C:\WINDOWS}
end;
數組的記憶體不是我們自己申請的, 系統會自動釋放; 記住: 只要是手動申請的記憶體一定要手動釋放.
我們給字元指標申請記憶體主要是為了在 API 中接受資料, 如果我們要直接賦給常量值, 系統會自動分配記憶體的, 譬如:
var
p: PChar;
begin
p := '萬一的 Delphi 部落格';
ShowMessage(p); {萬一的 Delphi 部落格}
end;
當然我們也可以用這種辦法申請記憶體, 就是笨了點, 譬如:
//擷取系統目錄
var
p: PChar;
begin
p := PChar(StringOfChar(Char(0), 256)); {反覆一個Null 字元 256 次成一個字串, 然後轉為 PChar}
GetSystemDirectory(p, StrBufSize(p));
ShowMessage(p); {C:\WINDOWS\system32}
end;
如果在 API 函數需要的字元指標是為了輸入, 當然也不需要申請記憶體, 譬如:
//設定視窗標題
var
p: PChar;
begin
p := '視窗新標題';
SetWindowText(Handle, p);
end;
//也可以直接給常量
begin
MessageBox(Handle, '提示資訊', '標題', MB_OK);
end;
//如果是給字串的變數或常量, 則需要轉換一下
var
str: string;
begin
str := '萬一的 Delphi 部落格';
TextOut(Canvas.Handle, 10, 10, PChar(str), Length(str));
{在表單上輸出文字, 此代碼不能在 OnCreate 事件中}
end;
跑題了...到現在已用到了 StrAlloc、StrDispose、WideStrAlloc、AnsiStrAlloc、StrBufSize 幾個函數.
還有 NewStr、DisposeStr、StrNew、StrDispose 也貌似有點關係.
先說 NewStr 和 DisposeStr(它們是一對);
NewStr 是根據 AnsiString 再建立一個 PAnsiString, 不過這是為相容而存在的, Delphi 已不提倡使用了.
不再提倡使用的函數都綴以 deprecated 標識, 並在代碼提示中用灰色顯示.
其實用 @ 即可擷取字串指標, 當然根本用不著它們.
還有個 StrNew; StrNew 可以再制一個相同的字元指標, 譬如:
var
p1,p2: PChar;
begin
p1 := 'Delphi';
p2 := StrNew(p1);
ShowMessageFmt('%s, %s', [p1, p2]); {Delphi, Delphi}
p1 := '2009';
ShowMessageFmt('%s, %s', [p1, p2]); {2009, Delphi}
StrDispose(p2); {釋放自己申請的}
end;
不過 StrNew 存在的意義也不大, 我們可以更簡單地完成上面的操作:
var
p1,p2: PChar;
begin
p1 := 'Delphi';
p2 := p1;
ShowMessageFmt('%s, %s', [p1, p2]); {Delphi, Delphi}
p1 := '2009';
ShowMessageFmt('%s, %s', [p1, p2]); {2009, Delphi}
end;
靜態數組, 在聲明時就分配好記憶體了, 譬如:
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 預設記憶體管理器的情況下, 我們還是不要直接使用它們.