Memory management: Overview(概述) 注意:Linux 使用glibc 函数,比如malloc,进行内存管理。要了解更多信息,请参考Linux 系统 关于malloc 的帮助文件。 在Windows 系统下,内存管理器负责程序中所有的动态内存分配和回收。New、Dispose、GetMem、 ReallocMem 和FreeMem 标准过程使用内存管理器,所有的对象和长字符串也通过内存管理器来进行 分配。 在 Windows 下,对于面向对象的程序和处理字符数据的程序,典型情况下,它们需要分配大量的较 小或中等大小的内存块,内存管理器对这种情况进行了优化。而其它的内存管理器,象GlobalAlloc 和 LocalAlloc 的实现方式以及Windows 支持的私有堆,在这种情形下性能并不好,当直接使用时,会降低 程序速度。 为确保最好的性能,内存管理器直接和 Win32 虚拟内存API(VirtualAlloc 和VirtualFree 函数) 打交道。内存管理器从操作系统中保留(reserve)地址空间时,以1MB 为一节(单位);当需要提交(commit) 物理内存时,以16KB 的幅度进行。当释放内存和地址空间时,也是以16KB 和1MB 为单位的。对于更 小的(内存)块,在已提交的内存中进行再分配。 内存管理器块总是以 4 个字节进行对齐,并总是拥有一个4 字节的头,这里包含内存块的大小及其 它信息位。这意味着,内存管理器块总是以双字的形式优化排列,以保证定位内存块时CPU 的效能发挥 得最好。 内存管理器维护着两个状态变量:AllocMemCount 和AllocMemSize,它们保存着当前分配的内 存块数目、以及这些内存块的总容量。在调试时,应用程序可以利用这些变量来显示状态信息。 System 单元提供了两个过程:GetMemoryManager 和SetMemoryManager,它们允许程序拦 截底层的内存管理器调用。System 单元还提供了GetHeapStatus 函数,它返回一个包含内存管理器详 细状态信息的记录结构。 Variables(变量) 全局变量在程序的数据段分配,并且在程序运行期间一直存在;局部变量(在过程或函数内声明) 存在于程序的堆栈中,每次调用过程或函数,局部变量进行分配,而调用结束后,局部变量被清除。编 译器优化可能提前消除变量(比如使用寄存器)。 注意:在Linux 下,堆栈大小只能由环境设置。 在 Windows 下,一个程序的堆栈由两个值定义:堆栈的最小值和最大值。这两个值受编译器指示字 $MINSTACKSIZE 和 $MAXSTACKSIZE 所控制,它们的缺省值分别是16,384(16K)和1,048,576 (1M)。程序保证拥有最小容量的堆栈空间,且不允许超过堆栈的最大值。如果空闲内存不能保证最小 的堆栈需求,Windows 在启动程序时会报告出错。 如果程序需要的堆栈容量超过最小值,它会以 4K 的幅度自动增加。如果分配额外的堆栈失败,可 能是没有足够的空闲内存,或者堆栈容量达到了它所允许的最大值。此时,将引发EStackOverflow 异 Memory management - 154 - 常(堆栈溢出检查完全是自动的。编译器指示字$S 原先是控制堆栈溢出检查的,保留它是为了向后兼 容性)。 在 Windows 或Linux 下,由GetMem 或New 过程创建的动态变量是在堆中分配的,除非使用 FreeMem 或Dispose 命令进行释放,否则它们将一直存在。 长字符串、宽字符串、动态数组、variant 以及接口在堆中进行分配,但它们的内存是自动管理的。 Integer types(整数类型) 整型变量的格式取决于于它的最小值和最大值边界: • 若边界都介于 -128..127(Shortint),存储为有符号字节(signed byte) • 若边界都介于 0..255 (Byte),存储为无符号字节(unsigned byte) • 若边界都介于 -32768..32767(Smallint),存储为有符号字(signed word) • 若边界都介于 0..65535(Word),存储为无符号字(unsigned word) • 若边界都介于 -2147483648..2147483647(Longint), 存储为有符号双字(signed double word) • 若边界都介于 0..4294967295(Longword),存储为无符号双字(unsigned double word) • 否则(Int64),存储为有符号四字(signed quadruple word) Character types(字符类型) Char、AnsiChar 或Char 的子界类型存储为一个无符号字节,WideChar 存储为一个无符号字。 Boolean types(布尔类型) Boolean 类型存储为Byte,ByteBool 也存储为Byte,WordBool 存储为Word,LongBool 存 储为Longint。 Boolean 类型把0 作为False,1 作为True;ByteBool、WordBool 和LongBool 把0 作为False, 非0 作为True。 Enumerated types(枚举类型) 若枚举类型的值不超过 256 个,并且在{$Z1} 状态(默认)下声明,它被存储为一个无符号字节; 若枚举类型的值超过256 个,或在{$Z2} 状态下声明,它被存储为一个无符号字;若枚举类型在{$Z4} 状态下声明,它被存储为无符号双字。 Real types(实数类型) The real types store the binary representation of a sign (+ or –), an exponent, and a significand. A real value has the form +/?significand * 2^exponent Memory management - 155 - where the significand has a single bit to the left of the binary decimal point. (That is, 0 <= significand < 2.) In the figures that follow, the most significant bit is always on the left and the least significant bit on the right. The numbers at the top indicate the width (in bits) of each field, with the leftmost items stored at the highest addresses. For example, for a Real48 value, e is stored in the first byte, f in the following five bytes, and s in the most significant bit of the last byte. Pointer types(指针类型) 指针类型有 4 个字节,作为一个32 位地址。指针值nil 存储为0。 Short string types(短字符串类型) 短字符串占用的字节数是它的最大长度加 1,第一个字节存储字符串的当前(动态)长度,剩下的 字节存储字符串的字符。 存储(字符串)长度的字节和字符都被看作无符号值。最大字符串长度是 255 个字符加上1 个保存 长度的字节(string[255])。 Long string types(长字符串) 长字符串变量是一个占用 4 字节内存的指针,指向一个动态分配的字符串。当字符串变量为空时(字 符串长度为零),指针为nil 并且不分配动态内存。对一个非空值,字符串指针指向一个动态分配的内存 块,这个内存块包含了字符串的实际内容,并且还有一个32 位的值来指示字符串的长度,同时还包括一 个32 位的引用计数。下面的表格说明了内存块的分配情况。 偏移量 内容 -8 32位引用计数 -4 长度(字节数) 0..Length-1 字符串 Length NULL 字符(#0) 内存块末尾的 NULL 字符由编译器和内置的字符串处理例程自动维护,这使得长字符串能直接转换 为一个零结尾字符串。 对字符串常量和文字串(literal),编译器象动态分配时一样为其分配内存,但把它的引用计数设置 为-1。当把字符串常量赋给一个字符串变量时,字符串指针指向为常量分配的内存块。当字符串引用记 数为-1 时,内置的字符串处理例程知道不能去修改它。 Wide string types(宽字符串) 在 Windows 下,宽字符串变量是一个占用4 字节内存的指针,指向一个动态分配的字符串。当字符 串变量为空时(字符串长度为零),指针为nil 并且不分配动态内存。对一个非空值,字符串指针指向一 个动态分配的内存块,这个内存块包含了字符串的实际内容,并且还有一个32 位的值来指示字符串的长 度(没有引用计数)。下面的表格说明了内存块的分配情况。 Memory management - 156 - 偏移量内容 -4 32位长度指示器(字节数) 0..Length -1 字符串 Length NULL 字符(#0) 字符串的长度以字节为单位,所以,它是字符串所包含的字符数目的两倍。 内存块末尾的 NULL 字符由编译器和内置的字符串处理例程自动维护,这使得宽字符串能直接转换 为一个零结尾字符串。 Set types(集合类型) 集合可看作是由位(bit)组成的数组,每个位指明一个元素是否在集合中。一个集合最多有256 个 元素,所以,一个集合占用的空间不会超过32 个字节。对一个特定的集合,它占用的字节数等于 (Max div 8) - (Min div 8) + 1 这里,Max 和Min 是集合基础类型的上下边界。集合中一个特定元素E 所在的字节(序号)是 (E div 8) - (Min div 8) 在这个字节中,它对应的位(序号)是 E mod 8 这里,E 表示元素的序数值。编译器尽可能把集合存储在CPU 寄存器中,但若它的大小比普通 Integer 类型大,或在程序的代码中使用了集合的地址,它总是被存储在内存中。 Static array types(静态数组) 静态数组是由它的元素按顺序构成的序列,拥有最小索引的元素在内存块的底端。对多维数组来说, 最右边的一维先发生变化。 Dynamic array types(动态数组) 动态数组变量是一个占用 4 字节内存的指针,指向动态分配内存的数组。当变量为空(未初始化) 或存储一个长度为0 的数组时,指针为nil,并且不会为数组分配内存。对一个非空数组,指针指向一个 动态分配的内存块,这个内存块包含了数组的值,并且还有一个32 位的长度指示、以及一个32 位的引 用计数。下面的表格说明了动态数组的内存分配情况。 偏移量 内容 -8 32位引用计数 -4 32位长度指示(元素个数) 0..Length * (size of element) - 1 数组的元素 Record types(记录类型) When a record type is declared in the {$A+} state (the default), and when the declaration does not include a Memory management - 157 - packed modifier, the type is an unpacked record type, and the fields of the record are aligned for efficient access by the CPU. The alignment is controlled by the type of each field. Every data type has an inherent alignment, which is automatically computed by the compiler. The alignment can be 1, 2, 4, or 8, and represents the byte boundary that a value of the type must be stored on to provide the most efficient access. The table below lists the alignments for all data types. 当一个记录类型在{$A+}状态下声明,并且没有使用packed 修饰字时,这是一个未压缩的(unpacked) 记录类型。此时,记录的各字段被优化排列,以使CPU 更有效地进行访问。排列规则由字段的类型控制, 每一种数据类型有固定的排列规则,并且由编译器自动计算。排列规则可以是1,2,4 或者8,它表示 每一个类型被存储的字节边界,以得到最优化的访问。下表列出了所有数据类型的排列规则。 类型 字节对齐 有序类型 类型的大小(1、2、4 或者8) 实数 2 for Real48, 4 for Single, 8 for Double and Extended 短字符串 1 数组 same as the element type of the array. 记录 the largest alignment of the fields in the record 集合 size of the type if 1, 2, or 4, otherwise 1 其它所有类型 4 To ensure proper alignment of the fields in an unpacked record type, the compiler inserts an unused byte before fields with an alignment of 2, and up to three unused bytes before fields with an alignment of 4, if required. Finally, the compiler rounds the total size of the record upward to the byte boundary specified by the largest alignment of any of the fields. 当在{$A–}状态下声明记录类型,或者当声明包含packed 修饰符时,记录的字段没有被优化排列,它们 被连续存放。这样一个压缩(packed)记录,它的大小是所有字段的大小之和。因为数据排列会改变, 所以,当把记录结构写入磁盘,或者通过内存把记录结构传递给由不同版本的编译器编译的模块时,使 用压缩记录是个好主意。 File types(文件类型) 文件类型表示为记录,类型文件和无类型文件占用 332 字节,它们的布局如下: type TFileRec = packed record Handle: Integer; Mode: word; Flags: word; case Byte of 0: (RecSize: Cardinal); 1: (BufSize: Cardinal; BufPos: Cardinal; BufEnd: Cardinal; BufPtr: PChar; OpenFunc: Pointer; InOutFunc: Pointer; FlushFunc: Pointer; CloseFunc: Pointer; Memory management - 158 - UserData: array[1..32] of Byte; Name: array[0..259] of Char; ); end; 文本文件占用460 字节,它的布局如下: type TTextBuf = array[0..127] of Char; TTextRec = packed record Handle: Integer; Mode: word; Flags: word; BufSize: Cardinal; BufPos: Cardinal; BufEnd: Cardinal; BufPtr: PChar; OpenFunc: Pointer; InOutFunc: Pointer; FlushFunc: Pointer; CloseFunc: Pointer; UserData: array[1..32] of Byte; Name: array[0..259] of Char; Buffer: TTextBuf; end; Handle 保存文件的句柄(当文件打开时)。 Mode 字段能被赋予下列值之一 const fmClosed = $D7B0; fmInput = $D7B1; fmOutput = $D7B2; fmInOut = $D7B3; 这里,fmClosed 表示文件已经被关闭,fmInput 和fmOutput 表示打开了(reset)一个文本文件(fmInput) 或创建并打开了(rewritten)一个新文本文件(fmOutput),fmInOut 表示打开或创建了一个类型或无类 型文件。任何其它的值表示文件变量还没有被赋值(因此没有被初始化)。 用户定义的写入例程使用 UserData 字段来存储数据。 Name 字段保存文件名,它是一个以0 字符(#0)结尾的字符序列。 对类型文件和无类型文件,RecSize 包含记录的长度(字节),Private 字段(?)没有使用但是保留的。 对文本文件,BufPtr 是指向一个缓冲区的指针,缓冲区的大小由BufSize 说明,BufPos 是缓冲区中下一 个要读写的字符的索引,BufEnd 是缓冲区中有效字符的数目。OpenFunc、InOutFunc、FlushFunc 和 CloseFunc 是指向I/O 例程的指针,请参考Device functions。Flags 决定了换行风格,像下面所示: bit 0 clear(清除0 位) LF line breaks(换行) bit 0 set(设置0 位) CRLF line breaks(回车换行) Flags 其它所有的位被保留以备将来使用。请参考DefaultTextLineBreakStyle 和SetLineBreakStyle。 Procedural types(过程类型) 过程指针存储为一个指向过程或函数入口点的 32 位指针;方法指针存储为一个指向方法入口点的32 位 指针,后面还跟一个指向对象的32 位指针。 Memory management - 159 - Class types(类类型) 类类型的值存储为一个 32 位的指针,它指向一个类的实例,我们称它为对象。对象的内部数据结构就象 一个记录,它的字段以声明的顺序进行存储,就象一系列连续的变量。对象的字段总是被优化排列,就 象未压缩的记录类型。从祖先类继承下来的所有字段存储在新声明的字段之前。 每个对象的前 4 个字节是一个指针,它指向类的虚方法表(VMT)。每个类有一个虚方法表,而不是每 个对象有一个。不同的类类型,不管多么相似,都不会共用VMT。VMT 由编译器自动创建,不能由程 序直接操纵。指向VMT 的指针,是由对象的构造函数自动存储的,也不能由程序直接操纵。 VMT 的布局如下表所示。在正偏移方向,VMT 包含一个由32 位方法指针构成的列表,每个指针对应于 类中用户定义的一个虚方法,方法指针的顺序和声明的顺序相一致,每个指针包含相应的虚方法的入口 地址。这种布局和C++的虚表(v-table)以及COM 兼容。在负偏移方向,VMT 也包含很多字段,它们 完成Object Pascal 的内部实现。应用程序应该使用Tobject 定义的方法来查询这一信息,因为在将来这种 实现方式有可能改变。 偏移量 类型 描述 -76 Pointer 指向虚方法表的指针(或nil) -72 Pointer 指向接口表的指针(或nil) -68 Pointer 指向自动化信息表的指针(或nil) -64 Pointer 指向实例初始化表的指针(或nil) -60 Pointer 指向类型信息表的指针(或nil) -56 Pointer 指向字段(定义)表的指针(或nil) -52 Pointer 指向方法(定义)表的指针(或nil) -48 Pointer 指向动态方法表的指针(或nil) -44 Pointer 指向包含类名的短字符串的指针 -40 Cardinal 实例的字节大小 -36 Pointer 指向祖先类的指针的指针(或nil)(指针的指针) -32 Pointer 指向SafecallException 方法入口指针的指针(或nil)(指针的指针) -28 Pointer AfterConstruction 方法入口(指针) -24 Pointer BeforeDestruction 方法入口(指针) -20 Pointer Dispatch方法入口(指针) -16 Pointer DefaultHandler 方法入口(指针) -12 Pointer NewInstance 方法入口(指针) -8 Pointer FreeInstance 方法入口(指针) -4 Pointer 析构函数Destroy 的入口地址(指针) 0 Pointer 用户自定义的第一个虚方法入口(指针) 4 Pointer 用户自定义的第二个虚方法入口(指针) ... ... ... Class reference types(类引用) 类引用(值)存储为一个 32 位指针,它指向一个类的虚方法表(VMT)。 Variant types(Variant 类型) variant 存储为一个16 字节的记录,它包含类型码,以及类型码指明的数据类型的值(或值的引用)。System Memory management - 160 - 和Variants 单元定义了variant 常量和类型。 TVarData 类型表示一个variant 变量的内部结构(在Windows 下,它和COM 以及Win32 API 中使用的 variant 是相同的),它能用来对variant 变量进行类型转换,以便访问变量的内部结构。 TVarData 记录的VType 字段包含了类型码,它存储在较低的12 个位中(每个位由varTypeMask 常量定 义)。并且,可能设置varArray 位用于表明variant 是数组,也可能设置varByRef 位用于表明variant 存 储的是值的引用而不是值本身。 TVarData 的Reserved1、Reserved2 和Reserved3 字段没有使用。 TVarData 记录剩余8 个字节的内容取决于VType 字段。若既没有设置varArray 位也没有设置varByRef 位,则它包含指定类型的值。 若设置了 varArray 位,variant 包含一个指向TVarArray 结构的指针,TVarArray 定义了数组,每个数组 元素的类型由Vtype 字段的varTypeMask 位指明。 若设置了 varByRef 位,variant 包含一个值的引用,它的类型由VType 字段的varTypeMask 和varArray 位指明。 varString 类型码是私有的,包含此类型的variant 不能传给非Delphi 函数。在Windows 下,当把variant 作为参数传给外部函数时,Delphi(的自动化支持)自动把varString 转换为varOleStr 类型。 在 Linux 下,不支持VT_decimal
Memory management(内存管理)
最新推荐文章于 2020-04-19 17:41:15 发布