http://blog.csdn.net/zang141588761/article/details/52838728

Delphi中堆栈区别

2016-10-17 14:49 277人阅读 评论(1) 收藏 举报
 分类:
Delphi(70) 

版权声明:本文为博主原创文章,未经博主允许不得转载。

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 ;

Delphi中堆栈区别的更多相关文章

  1. 在 DELPHI 中 procedure 型变量与 method 型变量的区别

    Procedure型变量: 在DELPHI中,函数.过程的地址可以赋给一个特殊类型的变量,变量可用如下方式声明: var p : procedure(num:integer); //过程 或: var ...

  2. Delphi中Self和Sender的区别

    在事件处理程序参数表中,至少含有一个参数Sender,它代表触发事件处理程序的构件,如在上例中,Sender就指Button2,有了Sender参数,可以使多个构件共用相同的事件处理程序,如下例:   ...

  3. Delphi中destroy, free, freeAndNil, release用法和区别

    Delphi中destroy, free, freeAndNil, release用法和区别 1)destroy:虚方法 释放内存,在Tobject中声明为virtual,通常是在其子类中overri ...

  4. delphi中exit,abort,break,continue 的区别

    from:http://www.cnblogs.com/taofengli288/archive/2011/09/05/2167553.html delphi中表示跳出的有break,continue ...

  5. Delphi中exit、break、continue等跳出操作的区别

    Delphi中表示跳出的有break,continue,abort,exit,halt,runerror等 1.break 强制退出最近的一层循环(注意:只能放在循环里:而且是只能跳出最近的一层循环) ...

  6. Java中堆栈的区别

    简单的说: Java把内存划分成两种:一种是栈内存,一种是堆内存. 在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配. 当在一段代码块定义一个变量时,Java就在栈中为这个变量分 ...

  7. Delphi 中的 procedure of object

    转载:http://www.cnblogs.com/ywangzi/archive/2012/08/28/2659811.html 总结:TMyEvent = procedure of object; ...

  8. Delphi 中的 procedure of object (类方法存在一个隐藏参数self),简单深刻 good

    其实要了解这些东西,适当的学些反汇编,WINDOWS内存管理机制,PE结构,看下李维的VCL架构剖析可以很好理解type TMyEvent = procedure of object;这是一种数据类型 ...

  9. 转发 Delphi中线程类TThread 实现多线程编程

    Delphi中有一个线程类TThread是用来实现多线程编程的,这个绝大多数Delphi书藉都有说到,但基本上都是对TThread类的几个成员作一简单介绍,再说明一下Execute的实现和Synchr ...

随机推荐

  1. JS的for循环包裹异步函数的问题

    有个循环,循环一个异步回调,为啥回调引用的循环值都是最后一步循环的循环值?然后,又有些时候无论什么循环值都得不到? var arr = [1,3,5,7,9]; var arrLength = arr ...

  2. django基础篇04-自定义simple_tag和fitler

    自定义simple_tag app目录下创建templatetags目录 templatetags目录下创建xxpp.py 创建template对象register,注意变量名必须为register ...

  3. git设置Eclipse中忽略的文件

    GitHub 官网样例文件https://github.com/github/gitignorehttps://github.com/github/gitignore/blob/master/Java ...

  4. VLAN原理详解[转载] 网桥--交换机---路由器

    来自:http://blog.csdn.net/phunxm/article/details/9498829 一.什么是桥接          桥接工作在OSI网络参考模型的第二层数据链路层,是一种以 ...

  5. redHat 安装mono 错误

    make[6]: * [do-install] Error 2 make[6]: Leaving directory /root/lindexi/mono-2.11.3/mcs/class/Syste ...

  6. MyBatis中批量insert

    在orcale和mybatis执行批量插入是不一样的. orcale如下:(这里要注意的是:useGeneratedKeys="false" ) 方式1:oracle批量插入使用 ...

  7. CSS的优先级理解

    样式的优先级 多重样式(Multiple Styles):如果外部样式.内部样式和内联样式同时应用于同一个元素,就是使多重样式的情况. 一般情况下,优先级如下: (外部样式)External styl ...

  8. 浅谈C++ allocator内存管理(对比new的局限性)(转)

    STL中,对内存管理的alloc的设计,迫使我去学习了allocator类.这里对allocator内存管理做了点笔记留给自己后续查阅.allocator类声明.定义于头文件<memory> ...

  9. APIO2019 题解

    APIO2019 题解 T1 奇怪装置 题目传送门 https://loj.ac/problem/3144 题解 很容易发现,这个东西一定会形成一个环.我们只需要求出环的长度就解决了一切问题. 设环的 ...

  10. POJ 3741 Raid (平面最近点对)

    $ POJ~3741~Raid $ (平面最近点对) $ solution: $ 有两种点,现在求最近的平面点对.这是一道分治板子,但是当时还是想了很久,明明知道有最近平面点对,但还是觉得有点不对劲. ...