Delphi 的内存操作函数-1,2,3,4

时间:2022-10-28 20:09:29

[ 1 ]: 给字符指针分配内存
马上能想到的函数有: 
________________________________________
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)); {反复一个空字符 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;
________________________________________

说来说去, 好像只有 StrAlloc 是我们值得我们记忆的?

还有一对非常重要的相关函数: GlobalAllocPtr、GlobalFreePtr; 它们的功能是上面这些都不可替代的!

GlobalAllocPtr 和 GlobalFreePtr 是对系统函数: GlobalAlloc、GlobalFree 的简化, 之所以说它们重要, 只是因为它们可以跨进程操作; 不过 GlobalAllocPtr 是给无类型指针(Pointer)分配内存, 当然就不仅仅用于字符指针了. 还是到后面专题再做例子吧. 
________________________________________

[ 2 ] 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 默认内存管理器的情况下, 我们还是不要直接使用它们. 
________________________________________


[ 3 ] Delphi 的内存操作函数(3): 给结构体指针分配内存 
使用结构或结构数组, 一般是不需要主动分配内存的, 譬如: 
________________________________________
var
pts: TPoint;
begin
pts.X := 1;
pts.Y := 2;
ShowMessageFmt('%d,%d', [pts.X, pts.Y]); {1,2}
end;

//结构数组:
var
Arr: array[0..2] of TPoint;
i: Integer;
begin
for i := 0 to Length(Arr) - 1 do
begin
    Arr[i].X := i;
    Arr[i].Y := Trunc(Sqr(i));
end;
ShowMessageFmt('%d,%d', [Arr[High(Arr)].X, Arr[High(Arr)].Y]); {2,4}
end;
________________________________________

但在很多时候, 一些参数是结构指针; 特别是在接受数据时, 一般需要手动分配内存. 如果只使用一个单结构指针, 用 New 分配内存是最合适的, 譬如: 
________________________________________
var
p: PPoint; {这是点结构 TPoint 的指针, 系统早定义好的}
begin
New(p);

// p^.X := 1; p^.Y := 2; {或者写成下面这样}
p.X := 1; p.Y := 2;

ShowMessageFmt('%d,%d', [p.X, p.Y]);
Dispose(p);
end;
________________________________________

更多时候, 我们需要给一个结构指针分配更多容量; GetMem 可以很容易地完成这个任务, 关键是如何访问. 譬如: 
________________________________________
var
p: PPoint;
begin
p := GetMemory(4 * SizeOf(TPoint)); {分配能容纳 4 个 TPoint 结构的内存}

{下面的代码访问了第一个结构, 其他 3 个怎么访问呢?}
p.X := 1; p.Y := 11;
ShowMessageFmt('%d,%d', [p.X, p.Y]); {1,11}

FreeMemory(p);
end;

//访问给结构指针分配的其他元素:
var
p: PPoint;
buf: array[0..255] of Char;
begin
p := GetMemory(4 * SizeOf(TPoint)); {分配能容纳 4 个 TPoint 结构的内存}

p.X := 1; p.Y := 11;

Inc(p); {指向第二个结构}
p.X := 2; p.Y := 22;

Inc(p); {指向第三个结构}
p.X := 3; p.Y := 33;

Inc(p); {指向第四个结构}
p.X := 4; p.Y := 44;

{既然用了 Inc, 那么在释放或使用前, 必须把指针退回到起始点!}
Dec(p, 3);

{读出看看; 注意这里的 wvsprintf 也是格式化函数, 有时它更方便}
wvsprintf(buf, '%d,%d; %d,%d; %d,%d; %d,%d', PChar(p));
ShowMessage(buf); {1,11; 2,22; 3,33; 4,44}

FreeMemory(p);
end;
________________________________________

如上的操作简直太残忍了, 幸亏数据少; 其实这种情况应该用数组, 这里提供一种更巧妙的办法 - 转换(为数组类型): 
________________________________________
var
p: PPoint;
i: Integer;
buf: array[0..255] of Char;
type
ArrPoint = array of TPoint; {用于转换的自定义类型}
begin
p := GetMemory(4 * SizeOf(TPoint));

for i := 0 to 3 do
begin
    ArrPoint(p)[i].X := i;
    ArrPoint(p)[i].Y := i * i;
end;

wvsprintf(buf, '%d,%d; %d,%d; %d,%d; %d,%d', PChar(p));
ShowMessage(buf); {0,0; 1,1; 2,4; 3,9}

FreeMemory(p);
end;
________________________________________


[ 4 ] Delphi 的内存操作函数(4): 清空与填充内存
FillMemory、ZeroMemory 一目了然的两个函数, 但其实它们都是调用了 FillChar;

清空不过就是填充空字符(#0: 编号为 0 的字符), 说来说去是一回事.

为了下面的测试, 先写一个以十六进制方式查看内存的函数: 
________________________________________
function GetMemBytes(var X; size: Integer): string;
var
pb: PByte;
i: Integer;
begin
pb := PByte(X);
for i := 0 to size - 1 do
begin
    Result := Result + IntToHex(pb^, 2) + #32;
    Inc(pb);
end;
end; {GetMemBytes end}

//测试:
var
p1: PAnsiChar;
p2: PWideChar;
s1: AnsiString;
s2: UnicodeString;
begin
p1 := 'ABCD';
p2 := 'ABCD';
s1 := 'ABCD';
s2 := 'ABCD';

ShowMessage(GetMemBytes(p1,4)); {41 42 43 44}
ShowMessage(GetMemBytes(p2,8)); {41 00 42 00 43 00 44 00}
ShowMessage(GetMemBytes(s1,4)); {41 42 43 44}
ShowMessage(GetMemBytes(s2,8)); {41 00 42 00 43 00 44 00}
end;
________________________________________

测试 FillMemory、ZeroMemory、FillChar 三个填充函数: 
________________________________________
const
num = 10;
var
p: PChar;
begin
p := StrAlloc(num);

ShowMessage(GetMemBytes(p, num)); {从结果看出 StrAlloc 没有初始化内存}

FillMemory(p, num, Byte('A'));
ShowMessage(GetMemBytes(p, num)); {41 41 41 41 41 41 41 41 41 41}

ZeroMemory(p, num);
ShowMessage(GetMemBytes(p, num)); {00 00 00 00 00 00 00 00 00 00}

FillChar(p^, num, 'B');
ShowMessage(GetMemBytes(p, num)); {42 42 42 42 42 42 42 42 42 42}

StrDispose(p);
end;
________________________________________

此时, 我想到一个问题: 
GetMem 和 GetMemory 没有初始化内存; AllocMem 会初始化内存为空, 那么
ReallocMem、ReallocMemory 会不会初始化内存?
测试一下(结果是没有初始化): 
________________________________________
{测试1}
var
p: Pointer;
begin
p := GetMemory(3);
ShowMessage(GetMemBytes(p, 3));
ReallocMem(p, 10);
ShowMessage(GetMemBytes(p, 10)); {没有初始化}
FreeMemory(p);
end;

{测试2}
var
p: Pointer;
begin
p := AllocMem(3);
ShowMessage(GetMemBytes(p, 3));
ReallocMem(p, 10);
ShowMessage(GetMemBytes(p, 10)); {没有初始化}
FreeMemory(p);
end;
________________________________________

另外: FillMemory、ZeroMemory 的操作对象是指针, 而 FillChar 的操作对象则是实体