收藏 分销(赏)

delphi中堆和栈的区别.doc

上传人:s4****5z 文档编号:8044098 上传时间:2025-02-01 格式:DOC 页数:7 大小:49.50KB 下载积分:10 金币
下载 相关 举报
delphi中堆和栈的区别.doc_第1页
第1页 / 共7页
delphi中堆和栈的区别.doc_第2页
第2页 / 共7页


点击查看更多>>
资源描述
delphi 堆和栈[转] 2.1 栈 栈是由操作系统在创建线程的时候,系统自动创建,栈是由顶像下分配的,DELPHI中默认的栈大小是1M,这个可以通过Project->Options->Linker->Max Stack size来改变其大小。 栈是线程执行代码的地方,操作系统根据系统调度算法来加载执行的代码,另外栈还存放函数的参数值,局部变量。栈的存取是按4字节偏移,不会根据需要动态增长,因此超出范围会报栈溢出。 2.2 堆 我们把在栈之外的分配内存都叫在堆上分配内存,堆是由程序员分配释放。在DELPHI中是用GetMem.inc中的代码来管理堆的,堆中包含许多大小不确定的块。初始状态下,堆仅有一个块,即堆本身。经过一段时间地取用和回收以后,堆中将可能只剩下一些“切割”后残余的“碎片”,且这些碎片可能已经无法再合并。此时,如果一个新的请求大于任何一个碎片,那么就必须再申请一个新的、大的块放在堆中。堆的使用永远是一个“拆东墙补西墙”的过程。 堆的大小是2G,在扩展内存模式下能达到3G。注意它与数据结构中的堆是两回事,它的分配方式类似于链表,访问“堆”的内容的时候需要先找到这个“堆”,然后再遍历链表,因此“堆”访问会比“栈”慢。 2.3 哪些在栈中 2.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; 知道了栈的首尾地址之后,我们就可以取出变量地址,然后和栈的地址比较,如果超出栈的范围,则表示变量在堆中。 2.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的地址。 2.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字节就是指针的地址。 2.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指向栈底,因此我们可以看出动态数组的内存是在栈中的。 2.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,从上面的信息我们可以看出结构体是在栈中的,结构体指针和指针一样。 2.4 哪些在堆中 用内存申请函数申请的内存都是在堆中的,如用New、GetMem、StrAlloc、AllocMem、SysGetMem,哪些自管理类型string、动态数组的内容都是在堆中的,下面我们给出结论,测试代码大家可以仿照上面的判断变量是否在栈中的代码编写。 2.4.1 指针指向的内容是在堆中 2.4.2 动态数组的内容是在堆中 2.4.3 String、ShortString、WideString的内容是在堆中 2.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都是在堆中的。 2.5 全局变量在堆中 全局变量的指针地址和指针内容都是在栈中的,我们把他归类到堆中。 2.6 栈和堆比较 2.6.1 栈和堆的管理方式比较 栈:由操作系统自动分配,而且在栈上分配内存是由编译器自动完成的,栈不需要编译器管理,操作系统自动实现申请释放; 堆:由操作系统提供接口,各个编译器实现管理方式,由外部程序申请释放,如果外部程序在程序结束时没有释放,由操作系统强行释放,在DELPHI中是用GetMem.inc来实现内存管理; 2.6.2 栈和堆的初始化比较 栈:分配的内存不会初始化,是一个垃圾值; 堆:分配的内存不会初始化,是一个垃圾值,但是DELPHI默认初始化类变量和全局变量; 2.6.3 栈和堆的申请方式比较 栈:由系统自动分配,如在函数申明一个局部变量i: Integer;编译器会自动在栈中分配内存; 堆:由程序自己管理,需要程序员自己申请,并指明大小; 2.6.4 堆和栈的效率比较 栈:在栈上分配空间是直接用add指令,对esp进行移位,例如add esp,-$44,可以在一个指令周期内完成; 堆:操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样代码中的FreeMem语句才能正确的释放本内存空间。 另外由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。 在堆中分配内存的时候会用HeapLock和HeapUnlock加锁,因此在多线程中分配内存是线性的,效率低下; 2.6.5 栈和堆的大小限制比较 栈:在Windows下栈默认大小是1M, 栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是固定的(是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。 堆:在Windows下默认堆大小是2GB,堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。 2.6.6 栈和堆的超出范围比较 栈:超出栈大小会报栈溢出,提示overflow; 堆:超出堆大小会报Out Of Memory; 原帖地址: 2.7 总结(zwh) 1、 基本数据类型变量和指针本身:局部变量在栈中,全局变量、类变量在堆中。 2、 指针指向的内容、动态数组、字符串类型(String、ShortString、WideString)、变体类型(Variant、OleVariant):不管局部变量还是全局变量、类变量都在堆中。 3、 DELPHI默认初始化堆中的变量; 2.8 线程中的堆栈(zwh) 1、 在一个进程的线程共享堆区,而进程中的线程各自维持自己栈。(C、Java也适用) 2、 也就是说,线程中,栈是私有的但堆是公用的。(C、Java也适用) 3、 因为栈是私有的但堆是公用的, 如果不同的线程都来使用一个全局变量有点乱套。为解决这个问题 Delphi 为我们提供了一个类似 var 的 ThreadVar 关键字, 线程在使用 ThreadVar 声明的全局变量时会在各自的栈中留一个副本, 这样就解决了冲突. 不过还是尽量使用局部变量, 或者在继承TThread 时使用类的成员变量, 因为 ThreadVar 的效率不好, 据说比局部变量能慢 10 倍.
展开阅读全文

开通  VIP会员、SVIP会员  优惠大
下载10份以上建议开通VIP会员
下载20份以上建议开通SVIP会员


开通VIP      成为共赢上传

当前位置:首页 > 百科休闲 > 其他

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        抽奖活动

©2010-2026 宁波自信网络信息技术有限公司  版权所有

客服电话:0574-28810668  投诉电话:18658249818

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :微信公众号    抖音    微博    LOFTER 

客服