Delphi内存专题
第一课:
Windows 是多任务的操作系统, 一个任务就是一个应用(应用程序)、一个应用占一个进程; 在一个进程里面, 又可以运行多个线程(所以就有了很多"多线程编程"的话题).
对 Win32 来讲, 系统给每个进程 4GB 的地址空间:
低端 2GB($00000000 - $7FFFFFFF) 给用户支配;
高端 2GB($80000000 - $FFFFFFFF) 留给系统使用.
文件或程序要调入内存才能工作, 先看看我们的内存到底有多大吧.
在系统盘根目录下有个 pagefile.sys 文件, 这就是我们的 "虚拟内存"(虚拟内存是以文件的形式存在的).
把 pagefile.sys 叫做 "虚拟内存" 似乎不妥, 所谓的 "虚拟" 只是相对真实的物理内存(RAM)来讲的; 很多书上的 "物理内存" 指的其实是: RAM + 虚拟内存, 也就是所有可用内存.
"虚拟内存" 在有些书上也被称作 "页文件" 、"页面文件" 或 "交换文件". "虚拟内存" 的大小可以从 "控制面板" 里设置, 默认是由系统自动管理的.
使用 "虚拟内存" 是系统的机制, 不管 RAM 有多大, 也应该使用 "虚拟内存".
RAM 大了, 系统就会少用 "虚拟内存", 从而提高速度; 但 RAM 也不是越大越好, 如果你真的放 4G 的内存条, 系统能够识别并使用的也就是 3G 左右, 因为 Win32 只有 4G 的管理能力(寻址能力), 当然这在 Win64 下要另当别论.
所谓系统给每个程序 4G, 是给 4G 的 "虚拟的地址表", 绝不是真实的内存, 不然一个记事本、一个计算器就得需要 8G.
这个 "虚拟的地址表" 在有些书上叫 "虚地址表"、"页映射表" 或 "虚内存地址", 也有叫 "虚拟内存地址", 很容易和 "虚拟内存" 的概念混淆.
这个 "虚拟的地址表" 上有 4G 个(4294967296 个)地址(0 - $FFFFFFFF), 虽然每个程序都有这样一个表, 但它们并不会冲突, 就因为这些地址是虚拟的, 系统在需要的时候会把它们映射成具体的真实内存的地址. 这样就阻断了一个进程对另一个进程的访问.
在 Win2000 以前的版本中, 用 GlobalAlloc 申请公用内存, 用 LocalAlloc 申请私有内存; 现在通过 "虚拟的地址表" 使用内存, 在进程中申请的内存都是私有的, 现在的 GlobalAlloc、LocalAlloc 没有区别, 都是执行同样的代码.
如果需要跨进程的公用内存空间, 需要用 "内存映射" 等手段, 这需要再专题学习.
总结概念: 物理内存、虚拟内存、虚地址表.
函数 GlobalMemoryStatus 可以获取它们的信息, 获取后放在 TMemoryStatus 结构中.
//TMemoryStatus 是 _MEMORYSTATUS 的重命名:
_MEMORYSTATUS = record
dwLength: DWORD; {结构长度}
dwMemoryLoad: DWORD; {表示已使用的内存比例的一个整数}
dwTotalPhys: DWORD; {物理内存总数}
dwAvailPhys: DWORD; {可用物理内存总数}
dwTotalPageFile: DWORD; {虚拟内存总数}
dwAvailPageFile: DWORD; {可用虚拟内存总数}
dwTotalVirtual: DWORD; {虚地址表中的地址总数}
dwAvailVirtual: DWORD; {虚地址表中可用的地址总数}
end;
做个小程序看看内存情况:
unit Unit1; interface uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls; type
TForm1 = class(TForm)
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
end; var
Form1: TForm1; implementation {$R *.dfm} procedure TForm1.FormCreate(Sender: TObject);
var
m: TMemoryStatus;
const
num = 1024 * 1024;
begin
GlobalMemoryStatus(m);
Memo1.Clear;
with Memo1.Lines do begin
Add(Format('dwLength:' + #9 + '%d', [m.dwLength]));
Add(Format('dwMemoryLoad:' + #9 + '%d', [m.dwMemoryLoad]));
Add(Format('dwTotalPhys:' + #9 + '%d', [m.dwTotalPhys div num]));
Add(Format('dwAvailPhys:' + #9 + '%d', [m.dwAvailPhys div num]));
Add(Format('dwTotalPageFile:' + #9 + '%d', [m.dwTotalPageFile div num]));
Add(Format('dwAvailPageFile:' + #9 + '%d', [m.dwAvailPageFile div num]));
Add(Format('dwTotalVirtual:' + #9 + '%d', [m.dwTotalVirtual div num]));
Add(Format('dwAvailVirtual:' + #9 + '%d', [m.dwAvailVirtual div num]));
end;
end; 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 默认内存管理器的情况下, 我们还是不要直接使用它们.
Delphi内存专题的更多相关文章
- Delphi内存操作API函数(备查,并一一学习)
Delphi内存操作API函数System.IsMemoryManagerSet;System.Move;System.New;System.ReallocMem;System.ReallocMemo ...
- Delphi 内存与指针
源:Delphi 内存与指针 Delphi 的内存操作函数(1): 给字符指针分配内存 Delphi 的内存操作函数(2): 给数组指针分配内存 Delphi 的内存操作函数(3): 给结构体指针分配 ...
- Delphi 内存分配 StrAlloc New(转)
源:Delphi 内存分配 StrAlloc New 引自:http://anony3721.blog.163.com/blog/static/5119742010824934164/ 给字符指针 ...
- 关于Delphi内存表的使用说明
关于Delphi内存表的使用说明: 1.建立临时表 数据输入是开发数据库程序的必然环节.在Client/Server结构中,客户端可能要输入一批数据后,再向服务器的后台数据库提交,这就需要在本地(客 ...
- delphi 内存映射
使用内存映射文件读写大文件 使用内存映射文件读写大文件 文件操作是应用程序最为基本的功能之一,Win32 API和MFC均提供有支持文件处理的函数和类.一般来说,这些函数可以满足大多数场合的要求,但是 ...
- Delphi内存管理(Integer、Boolean、Record、枚举等都是在作用域内编译器自动申请内存,出了作用域自动释放;另外,字符串、Variant、动态数组、接口也是由Delphi自动管理)
一.什么是堆.栈? 程序需要的内存空间分为 heap(堆) 和 stack(栈),heap 是自由存储区, stack 是自动存储区,使用 heap 需要手动申请.手动释放, stack 是自动申请. ...
- 先有Delphi内存对象,后有句柄(如果需要的话),最后再显示
在设计期放上一个Panel1和Button1,然后设置Panel1.Visible:=False 这时候执行: procedure TForm1.Button4Click(Sender: TObjec ...
- delphi内存映射 与 映射数据获取
一.原理 通过使用“内存映射文件”,实现内存共享 二.主要操作 共享内存结构: PShareMem = ^TShareMem; TShareMem = Record id:stri ...
- delphi 内存泄露 分析
随机推荐
- hbase完整分布式集群搭建
简介: hadoop的单机,伪分布式,分布式安装 hadoop2.8 集群 1 (伪分布式搭建 hadoop2.8 ha 集群搭建 hbase完整分布式集群搭建 hadoop完整集群遇到问题汇总 Hb ...
- 《我是一只IT小小鸟》读书笔记——第七周
我是一只IT小小鸟,每一个程序员都是从这样的阶段成长起来的,问题是是否能一开始就找到正确的路径,少走弯路.本书收集了许多年轻程序员从大学开始到就业的成长之路,十分有指导价值也很让人深思. 切忌急功近利 ...
- 根据不同配置.env获取不同的配置文件的配置
env 不同环境 1. 安装扩展 composer require vlucas/phpdotenv 2 . 创建文件 .env .env.test .env.production .env.exam ...
- Yii2模型介绍
通过来说,我们可以把yii2中的Mdoel分为两种: 1)数据模型: 2)表单模型: 数据模型 数据模型关联数据表,用来实现对数据的操作; 一般数据模型放在common/models下: 表单模型 表 ...
- static 关键字在java语言中的特性
一,将自己注入到一个静态变量中实现静态类,如下写法 以上方法的目的是要实现一个静态类,方便用类名获取对象实例,一般情况下调用普通方法需要对象实例.这对象要么new出来,要么spring的注入如下是 ...
- linux设备驱动程序-i2c(1):i2c总线的添加与实现
linux设备驱动程序-i2c(1):i2c总线的添加与实现 (基于4.14内核版本) 在上一章节linux设备驱动程序-i2c(0)-i2c设备驱动源码实现中,我们演示了i2c设备驱动程序的源码实现 ...
- Kali和Metasploitable2的网络配置
Kali和Metasploitable2的网络配置 2017年06月19日 16:00:00 weixin_34275734 阅读数 389 原文链接:https://blog.csdn.net/ ...
- 微信小程序和APP优劣势大对比
小程序的优势: 1. 无需下载,随走随关 2. 功能丰富,体验更简便 3. 接口众多,可以进行不断的开发 4. 流量入口大,背靠日活9.6亿的微信 5. 有强大的微信生态环境 小程序对比APP的好处: ...
- 《BUG创造队》第八次团队作业:Alpha冲刺
项目 内容 这个作业属于哪个课程 2016级软件工程 这个作业的要求在哪里 实验十二 团队作业8:软件测试与ALPHA冲刺 团队名称 BUG创造队 作业学习目标 (1)掌握软件测试基础技术.(2)学习 ...
- 192-0070 Final project proposal
Final project proposal192-00701 – Summary of your project.It is based on an existing game which is c ...