DELPHI栈和堆

SQLDebug_Fan 2010-07-15 03:53:41
加精
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 栈和堆的超出范围比较
栈:超出栈大小会报栈溢出;
堆:超出堆大小会报Out Of Memory;

原帖地址:http://blog.csdn.net/SQLDebug_Fan/archive/2010/07/15/5737329.aspx
...全文
3214 135 打赏 收藏 转发到动态 举报
写回复
用AI写文章
135 条回复
切换为时间正序
请发表友善的回复…
发表回复
hhj0301 2010-09-29
  • 打赏
  • 举报
回复
长进了,谢谢分享!
cq_mark 2010-09-13
  • 打赏
  • 举报
回复
又学习了新知识了
yyzzzh 2010-08-12
  • 打赏
  • 举报
回复
栈和堆的理解 讲的很详细,支持一下!
hucaiyu2010 2010-08-12
  • 打赏
  • 举报
回复
堆和栈其实很简单;
比如局部变量和stdcall的参数就在栈里面,new或create的就在内存堆里面
AsheBin 2010-08-11
  • 打赏
  • 举报
回复
好人啊,送知识还送分!
fenshm 2010-08-11
  • 打赏
  • 举报
回复
接分生娃开宝马
modney 2010-08-11
  • 打赏
  • 举报
回复
呵呵,沙发。。。。。
IVC2009 2010-07-26
  • 打赏
  • 举报
回复
有点不明白
qiaoyan1981 2010-07-26
  • 打赏
  • 举报
回复
还是JAVA的垃圾回收机制自动一些,其他面向对象语言都没做这个
SQLDebug_Fan 2010-07-23
  • 打赏
  • 举报
回复
[Quote=引用 125 楼 rainychan2009 的回复:]

引用 44 楼 lhylhy 的回复:
这文章看不出Delphi的特色呀?

windows上的内存管理!
[/Quote]
在Windows上各个语言基本相同,这篇文档除了示例代码是DELPHI的,其它都可以用于别的语言。
rainychan2009 2010-07-23
  • 打赏
  • 举报
回复
[Quote=引用 44 楼 lhylhy 的回复:]
这文章看不出Delphi的特色呀?
[/Quote]
windows上的内存管理!
好兄弟好兄弟 2010-07-21
  • 打赏
  • 举报
回复
不错,不错,进来学习了。
xiaoxiangqing 2010-07-21
  • 打赏
  • 举报
回复
说得太详细了,谢谢分享
ximenziplc 2010-07-21
  • 打赏
  • 举报
回复
Delphi
peacewong 2010-07-21
  • 打赏
  • 举报
回复
个人感觉delphi是个不错的编译环境,很喜欢
yaolei007 2010-07-21
  • 打赏
  • 举报
回复
很不错~~谢谢分享
seastars 2010-07-21
  • 打赏
  • 举报
回复
相当高级深入的知识
jornye 2010-07-20
  • 打赏
  • 举报
回复
[Quote=引用 95 楼 tan124 的回复:]
学习下....
[/Quote]
只有引用的内容不允许回复!
haibuku 2010-07-20
  • 打赏
  • 举报
回复
学一下
llwlz 2010-07-20
  • 打赏
  • 举报
回复
路过,接分
加载更多回复(107)
因权限不够,只能上传20M,故分两部分上传 提供了有关使用算法和数据结构的一个详尽的介绍。Bucknall先从算法性能的讨论开始,涵盖了诸如数组、链表和二叉树等内容。这本书强调了查找算法(如顺序和二分查找),另外也重点介绍了排序算法(包括冒泡排序、插入排序、希尔排序、快速排序和排序),此外还提供了有关的优化技术。不仅如此,作者还介绍了散列和散列表、优先队列、状态机和正则表达式以及诸如哈夫曼和LZ77等数据压缩技术。 随附光盘中有作者所开发的一个相当成功的自由软件库EZDSL,另外还有可运行于各版本Delphi上和Kylix上的源代码,此外还提供了TurboPower Software公司的可执行程序。 目录 前言 致谢 第1章什么是算法 1.1什么是算法 1.2算法和平台 1.3调试与测试 1.4小结 第2章数组 2.1数组 2.2Delphi中的数组类型 2.3TList类和指针数组 2.4磁盘数组 2.5小结 第3章链表、和队列 3.1单链表 3.2双向链表 3.3链表的优缺点 3.4 3.5队列 3.6小结 .第4章查找 4.1比较例程 4.2顺序查找 4.3二分查找 4.4小结 第5章排序 5.1排序算法 5.2排序基础知识 5.3小结 第6章随机算法 6.1随机数生成 6.2其他随机数分布 6.3跳表 6.4小结 第7章散列和散列表 7.1散列函数 7.2利用线性探测方法实现冲突解决 7.3其他开放定址机制 7.4利用链式方法解决冲突 7.5利用桶式方法解决冲突 7.6磁盘上的散列表 7.7小结 第8章二叉树 8.1创建一个二叉树 8.2叉树的插入和删除 8.3二叉树的遍历 8.4二叉树的类实现 8.5二叉查找树 8.6伸展树 8.7红黑树 8.8小结 第9章 优先队列和排序 9.1优先队列 9.2 9.3排序 9.4扩展优先队列 9.5小结 第10章 状态机和正则表达式 10.1状态机 10.2正则表达式 10.3小结 第11章数据压缩 11.1数据表示 11.2数据压缩 11.3位流 11.4最小冗余压缩 11.5字典压缩 11.6小结 第12章 高级主题 12.1读者-写者算法 12.2生产者-消费者算法 12.3查找两文件的差别 12.4小结 后记

1,183

社区成员

发帖
与我相关
我的任务
社区描述
Delphi Windows SDK/API
社区管理员
  • Windows SDK/API社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧