Delphi中堆栈区别

    xiaoxiao2021-12-10  12

    1      栈是由操作系统在创建线程的时候,系统自动创建,栈是由顶像下分配的,   DELPHI   中默认的栈大小是   1M   ,这个可以通过   Project->Options->Linker->Max Stack size   来改变其大小。 栈是线程执行代码的地方,操作系统根据系统调度 算法 来加载执行的代码,另外栈还存放函数的参数值,局部变量。栈的存取是按   4   字节偏移,不会根据需要动态增长,因此超出范围会报栈溢出。   2      我们把在栈之外的分配内存都叫在堆上分配内存,堆是由程序员分配释放。在   DELPHI   中是用   GetMem.inc   中的代码来管理堆的,堆中包含许多大小不确定的块。初始状态下,堆仅有一个块,即堆本身。经过一段时间地取用和回收以后,堆中将可能只剩下一些“切割”后残余的“碎片”,且这些碎片可能已经无法再合并。此时,如果一个新的请求大于任何一个碎片,那么就必须再申请一个新的、大的块放在堆中。堆的使用永远是一个“拆东墙补西墙”的过程。 堆的大小是   2G   ,在扩展内存模式下能达到   3G   。注意它与 数据结构 中的堆是两回事,它的分配方式类似于链表,访问“堆”的内容的时候需要先找到这个“堆”,然后再遍历链表,因此“堆”访问会比“栈”慢。 3      哪些在栈中 3.1               获取栈的首尾地址 获取通常情况下的栈地址 在写汇编的时候,我们知道   esp   存放栈顶指针,   ebp   存放栈底指针 procedure GetStackAddress(var AStackTop, AStackBottom: Cardinal); begin   asm     mov [eax], esp;     //   栈顶,   eax   接收第一个参数     mov [edx], ebp;     //   栈底,   edx   接收第二个参数   end; end; 获取异常发生时的栈地址             Windows   下,   FS:[4]   存放发生异常时的栈顶指针。 procedure GetStackAddress(var AStackTop, AStackBottom: Cardinal); begin   asm     mov ecx, FS:[4];  //FS:[4]   放置发生异常时的栈信息     sub ecx, 3;     mov [eax], eax;   //   栈顶,   eax   接收第一个参数     mov [edx], ebp;   //   栈低,   edx   接收第二个参数   end; end; 知道了栈的首尾地址之后,我们就可以取出变量地址,然后和栈的地址比较,如果超出栈的范围,则表示变量在堆中。 3.2               基本数据类型(   Integer     Cardinal     Shortint     Smallint     Longint     Int64     Byte     Word     LongWord     Char   基本类型在函数体内分配是在栈中的,如果在类中分配则是在堆中的。另外   Int64   也是在栈中分配的,它具体的分配是偏移   8   字节。我们写下如下测试代码: procedure TestInt64; var   Value: Int64;   StackTop, StackBottom: Cardinal; begin   Value := 10;   GetStackAddress(StackTop, StackBottom);   ShowMessage(Format('StackTop: %s, StackBottom: %s; Int64 Address: %s',     [IntToHex(StackTop, 8), IntToHex(StackBottom, 8), IntToHex(Integer(@Value), 8)])); end; 我电脑测试显示的信息为   StackTop: 0012F5E0, StackBottom: 0012F628; Int64 Address: 0012F620   ,从上面信息我们可以看出栈底偏   8   字节就是   Value   的地址。 3.3               指针类型是指针在栈中,指针所指向的地址在堆中 指针在函数体内分配,指针的地址是在栈中的,指针的内容是在堆中的。指针如果在类中分配则,指针地址和指针内容都是在堆中的。我们写下如下测试代码: procedure TestPointer; var   APoint: Pointer;   StackTop, StackBottom: Cardinal; begin   GetMem(APoint, 1000);   GetStackAddress(StackTop, StackBottom);   ShowMessage(Format('StackTop: %s, StackBottom: %s; Pointer Address: %s; Pointer Content Address: %s',     [IntToHex(StackTop, 8), IntToHex(StackBottom, 8), IntToHex(Integer(@APoint), 8),     IntToHex(Integer(APoint), 8)])); end; 我的电脑测试显示的信息为   StackTop: 0012F568, StackBottom: 0012F5B8; Pointer Address: 0012F5B4; Pointer Content Address: 00A3FD10   ,从上面的信息我们可以栈底偏   4   字节就是指针的地址。 3.4               固定数组 固定数组在函数体内分配是在栈中的,如果在类中分配则是在堆中的。因此不能函数体内分配超过   1M   大小的固定数组,否则会造成栈溢出。我们写下如下测试代码: type   TFixArray = array[0..9] of Integer; procedure TestFixArray; var   FixArray: TFixArray;   StackTop, StackBottom: Cardinal; begin   FixArray[0] := 10;   GetStackAddress(StackTop, StackBottom);   ShowMessage(Format('StackTop: %s, StackBottom: %s; Int64 Address: %s',     [IntToHex(StackTop, 8), IntToHex(StackBottom, 8), IntToHex(Integer(@FixArray[0]), 8)])); end; 我的电脑测试显示的信息为   StackTop: 0012F550, StackBottom: 0012F5B8; Fix Array Address: 0012F588   ,从上面的信息我们可以看出固定数组是在栈中的,动态数组类似指针,只是动态数组的指针在栈中,动态数组的内容是在堆中的。另外我们从汇编代码也可以看出相同的信息,   FixArray[0] := 10   对应的汇编代码是   mov [ebp-$30],$0000000a     ebp   指向栈底,因此我们可以看出动态数组的内存是在栈中的。 3.5               结构体 结构体在函数体内分配是在栈中的,在类中分配则是在堆中的,如果结构体内含有   string   等指针类型,则指针的地址在站内,指针的内容是在堆中的。在函数体内分配超过   1M   大小的结构体也会造成栈溢出。我们写下如下测试代码: type   TRecord = record     Value: string;     Len: Integer;   end; procedure TestRecord; var   PntRecord: TRecord;   StackTop, StackBottom: Cardinal; begin   PntRecord.Value := 'Test';   GetStackAddress(StackTop, StackBottom);   ShowMessage(Format('StackTop: %s, StackBottom: %s; Record Address: %s; Record Pointer Address: %s',     [IntToHex(StackTop, 8), IntToHex(StackBottom, 8), IntToHex(Integer(@PntRecord), 8),     IntToHex(Integer(PChar(PntRecord.Value)), 8)])); end; 我的电脑测试显示的信息为   StackTop: 0012F564, StackBottom: 0012F5B8; Record Address: 0012F5B0; Record Pointer Address: 0045F4B4   ,从上面的信息我们可以看出结构体是在栈中的,结构体指针和指针一样。 4      哪些在堆中 用内存申请函数申请的内存都是在堆中的,如用   New     GetMem     StrAlloc     AllocMem     SysGetMem   ,哪些自管理类型   string   、动态数组的内容都是在堆中的,下面我们给出结论,测试代码大家可以仿照上面的判断变量是否在栈中的代码编写。 4.1               指针指向的内容是在堆中 4.2               动态数组的内容是在堆中 4.3               String     ShortString     WideString   的内容是在堆中 4.4               变体   Variant     OleVariant   的内容是在堆中 变体类型是一个结构体,它的定义是: TVarData = packed record     case Integer of       0: (VType: TVarType;           case Integer of             0: (Reserved1: Word;                 case Integer of                   0: (Reserved2, Reserved3: Word;                       case Integer of                         varSmallInt: (VSmallInt: SmallInt);                         varInteger:  (VInteger: Integer);                          varSingle:   (VSingle: Single);                         varDouble:   (VDouble: Double);                         varCurrency: (VCurrency: Currency);                         varDate:     (VDate: TDateTime);                         varOleStr:   (VOleStr: PWideChar);                         varDispatch: (VDispatch: Pointer);                         varError:    (VError: HRESULT);                         varBoolean:  (VBoolean: WordBool);                         varUnknown:  (VUnknown: Pointer);                          varShortInt: (VShortInt: ShortInt);                         varByte:     (VByte: Byte);                         varWord:     (VWord: Word);                         varLongWord: (VLongWord: LongWord);                         varInt64:    (VInt64: Int64);                         varString:   (VString: Pointer);                         varAny:      (VAny: Pointer);                         varArray:    (VArray: PVarArray);                         varByRef:    (VPointer: Pointer);                       );                   1: (VLongs: array[0..2] of LongInt);                );             2: (VWords: array [0..6] of Word);             3: (VBytes: array [0..13] of Byte);           );       1: (RawData: array [0..3] of LongInt);        end;           从定义中我们可以看出   varOleStr     varString     varArray     varByRef   都是在堆中的。 5      全局变量在堆中 全局变量的指针地址和指针内容都不是在栈中的,我们把他归类到堆中。 6      栈和堆比较 6.1               栈和堆的管理方式比较 栈:由操作系统自动分配,而且在栈上分配内存是由编译器自动完成的,栈不需要编译器管理,操作系统自动实现申请释放; 堆:由操作系统提供接口,各个编译器实现管理方式,由外部程序申请释放,如果外部程序在程序结束时没有释放,由操作系统强行释放,在   DELPHI   中是用   GetMem.inc   来实现内存管理; 6.2               栈和堆   的初始化比较 栈:分配的内存不会初始化,是一个垃圾值; 堆:分配的内存不会初始化,是一个垃圾值,但是   DELPHI   默认初始化类变量和全局变量; 6.3               栈和堆的申请方式   比较 栈:由系统自动分配,如在函数申明一个局部变量   i: Integer   ;编译器会自动在栈中分配内存; 堆:由程序自己管理,需要程序员自己申请,并指明大小; 6.4               堆和栈的效率比较 栈:在栈上分配空间是直接用   add   指令,对   esp   进行移位,例如   add esp,-$44   ,可以在一个指令周期内完成; 堆:   操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样代码中的 FreeMem   语句才能正确的释放本内存空间。另外由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。   在堆中   分配内存的时候会用   HeapLock     HeapUnlock   加锁,因此在多线程中分配内存是线性的,效率低下; 6.5               栈和堆的大小限制   比较 栈:在   Windows   下栈默认大小是   1M,  栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在   WINDOWS   下,栈的大小是固定的(是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示   overflow   。因此,能从栈获得的空间较小。 堆:在   Windows   下默认堆大小是   2GB   ,堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。 6.6               栈和堆   的超出范围比较 栈:超出栈大小会报栈溢出; 堆:超出堆大小会报   Out Of Memory  
    转载请注明原文地址: https://ju.6miu.com/read-700385.html

    最新回复(0)