第一课:编程
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 没有区别, 都是执行一样的代码.
若是须要跨进程的公用内存空间, 须要用 "内存映射" 等手段, 这须要再专题学习.dom
总结概念: 物理内存、虚拟内存、虚地址表.
函数 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)]); {数组大小分别是: 5十二、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 的区别:
一、FreeMemory 会检查是否为 nil 再 FreeMem, 这有点相似: Free 与 Destroy;
二、FreeMem 还有个默认参数能够指定要释放的内存大小, 不指定就所有释放(不必只释放一部分吧);
三、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 默认内存管理器的状况下, 咱们仍是不要直接使用它们.