对Object Pascal编译器给类对象分配堆内存细节的一种大胆猜测
- 格式:doc
- 大小:57.00 KB
- 文档页数:12
Delphi资料项目 (2)Delphi程序设计基本步骤 (4)Object Pascal语言 (6)Delphi对象与类类型 (29)Delphi的程序单元 (44)处理异常 (46)包 (50)项目与项目有关的文件Delphi项目包含窗体、单元、资源、选项等,所有这些信息都驻留在磁盘文件中。
在设计程序时,Delphi 创建这些文件的大部分,但有一些文件需要你借助其它工具来创建或其它途径获得,如资源文件、帮助文件等。
下面是设计程序时,Delphi自动创建的文件:?项目文件(.Dpr):Delphi项目文件,用于保存窗体、单元等的信息,以及程序运行的初始化代码等,这种文件实际上包含了Pascal源代码。
?单元文件(.pas):Pascal文件,用于保存程序源代码,可以是与窗体有关的单元或是独立的单元。
?窗体文件(.Dfm):保存窗体或数据模块及其构件特性的二进制文件。
?选项文件(.Dof):含有当前项目选项设置的文本文件。
?Package源文件(.DPK):软件包的项目源代码文件,用于管理Packages信息。
?资源文件(.res):该二进制文件包含项目的图标,由Delphi不断更新和创建,用户一般不需要修改。
?备份文件(.-dp,.-df,-pa):分别对应项目、窗体、单元文件的备份文件。
?Desktop文件(.DSK):包含了与Delphi窗口的位置、在编辑器中打开的文件及其它桌面设置有关的信息。
?类型库(.TLB):一种自动建立或由类型库编辑器为OLE服务器端应用程序建立的。
?项目配置文件(.CFG):保存项目配置,文件名与项目名相同,但后缀为.CFG。
?Code Insight配置文件(.DCI):保存IDE中对Code Insight的修改信息。
?构件板配置文件(.DCT):保存IDE中对构件板的修改信息。
?菜单配置文件(. DMT):保存IDE中对Delphi菜单的修改信息。
?容器库修改文件(. DMT):保存程序对Delphi菜单容器库的修改信息。
C语言中内存四区的详解C语言编程2022-05-10 14:00来自:今日头条,作者:抖点料er链接:https:///article/7046019680989037069/1、内存四区1.1数据类型本质分析1.1.1数据类型的概念•“类型”是对数据的抽象•类型相同的数据有相同的表示形式、存储格式以及相关的操作•程序中使用的所有数据都必定属于某一种数据类型1.1.2数据类型的本质•数据类型可理解为创建变量的模具:是固定内存大小的别名。
•数据类型的作用:编译器预算对象(变量)分配的内存空间大小。
•注意:数据类型只是模具,编译器并没有分酤空间,只有根据类型(模具)创建变量(实物),编译器才会分配空间。
1.2变量的本质分析1.2.1变量的概念概念:既能读又能写的内存对象,称为变量;若一旦初始化后不能修改的对象则称为常量。
变量定义形式:类型标识符,标识符,…,标识符;1.2.2变量的本质1、程序通过变量来申请和命名内存空间int a = 0。
2、通过变量名访问内存空间。
1.3程序的内存四区模型流程说明1、操作系统把物理硬盘代码load到内存2、操作系统把c代码分成四个区栈区( stack):由编译器自动分配释放,存放函数的参数值,局部变量的值等堆区(heap):一般由程序员分配释放(动态内存申请与释放),若程序员不释放程序结束时可能由操作系统回收全局区(静态区)(statIc):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,该区域在程序结束后由操作系统释放常量区:字符串常量和其他常量的存储位置,程序结束后由操作系统释放。
程序代码区:存放函数体的二进制代码。
3、操作系统找到main函数入口执行1.4函数调用模型1.5函数调用变量传递分析(1)(2)(3)(4)(5)1.5栈的生长方向和内存存放方向相关代码:02_数据类型本质.c#define _CRT_SECURE_NO_WARNINGS #include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>int main(){int a;//告诉编译器,分配4个字节int b[10];//告诉编译器,分配4*10个字节/*类型本质:固定内存块大小别名可以通过sizeof()测试*/printf("sizeof(a)=%d,sizeof(b)=%d\n", sizeof(a), sizeof(b));//打印地址//数组名称,数组首元素地址,数组首地址printf("b:%d,&b:%d\n",b,&b);//地址相同//b,&b数组类型不同//b,数组首地址元素一个元素4字节,+1 地址+4//&b,整个数组首地址一个数组4*10=40字节, +1 地址+40 printf("b+1:%d,&b+1:%d\n", b + 1, &b + 1);//不同//指针类型长度,32位机器32位系统下长度是 4字节// 64 64 8char********* p = NULL;int* q = NULL;printf("%d,%d\n", sizeof(p), sizeof(q));//4 , 4return0;}03_给类型起别名.c#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>typedef unsigned int u32;//typedef 和结构体结合使用struct Mystruct{int a;int b;};typedef struct Mystruct2{int a;int b;}TMP;/*void 无类型1.函数参数为空,定义函数时用void修饰 int fun(void)2.函数没有返回值:使用void void fun (void)3.不能定义void类型的普通变量:void a;//err 无法确定是什么类型4.可以定义 void* 变量 void* p;//ok 32位系统下永远是4字节5.数据类型本质:固定内存块大小别名6.void *p万能指针,函数返回值,函数参数*/int main(){u32 t;//unsigned int//定义结构体变量,一定要加上struct 关键字struct Mystruct m1;//Mystruct m2;//errTMP m3;//typedef配合结构体使用struct Mystruct2m4;printf("\n");return0;}04_变量的赋值.c#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>int main(){//变量本质:一段连续内存空间别名int a;int* p;//直接赋值a = 10;printf("a=%d\n", a);//间接赋值printf("&a:%d\n", &a);p = &a;printf("p=%d\n", p);*p = 22;printf("*p=%d,a=%d\n", *p, a);return0;}05_全局区分析.c#define _CRT_SECURE_NO_WARNINGS #include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>int main(){//变量本质:一段连续内存空间别名int a;int* p;//直接赋值a = 10;printf("a=%d\n", a);//间接赋值printf("&a:%d\n", &a);p = &a;printf("p=%d\n", p);*p = 22;printf("*p=%d,a=%d\n", *p, a);return0;}06_堆栈区分析.c#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>char* get_str(){char str[] = "abcdef";//内容分配在栈区,函数运行完毕后内存释放printf("%s\n", str);return str;}char* get_str2(){char* temp = (char*)malloc(100);if (temp == NULL){return NULL;}strcpy(temp, "abcdefg");return temp;}int main(){char buf[128] = { 0 };//strcpy(buf,get_str());//printf("buf = %s\n", buf);//乱码,不确定内容char* p = NULL;p = get_str2();if (p != NULL){printf("p=%s\n", p);free(p);p = NULL;}return0;}07_静态局部变量.c#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>int* getA(){static int a = 10;//在静态区,静态区在全局区return &a;}int main(){int* p = getA();*p = 5;printf("%d\n",);return0;}08_栈的生长方向.c#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>int* getA(){static int a = 10;//在静态区,静态区在全局区return &a;}int main(){int* p = getA();*p = 5;printf("%d\n",);return0;}版权申明:内容来源网络,版权归原创者所有。
另一方面, 要注意: 我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,创建了String类的对象str。
担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的对象。
只有通过new()方法才能保证每次都创建一个新的对象。
由于String类的immutable性质,当String变量需要经常变换 其值时,应该考虑使用StringBuffer类,以提高程序效率。
1. 首先String不属于8种基本数据类型,String是一个对象。
因为对象的默认值是null,所以String的默认值也是null;但它又是一种特殊的对象,有其它对象没有的一些特性。
2. new String()和new String(”")都是申明一个新的空字符串,是空串不是null; 3. String str=”kvill”;String str=new String (”kvill”)的区别示例: String s0="kvill"; String s1="kvill"; String s2="kv" + "ill"; System.out.println( s0==s1 ); System.out.println( s0==s2 ); 结果为:true true 首先,我们要知结果为道JAVA 会确保一个字符串常量只有一个拷贝。
因为例子中的 s0和s1中的”kvill”都是字符串常量,它们在编译期就被确定了,所以s0==s1为true;而"kv”和"ill”也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中” kvill”的一个引用。
所以我们得出s0==s1==s2;用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。
堆内存和栈内存详解(转载)堆:挨次随便栈:先进后出堆和栈的区分一、预备学问一程序的内存安排一个由C/C++编译的程序占用的内存分为以下几个部分1、栈区(stack)-由编译器自动安排释放,存放函数的参数值,局部变量的值等。
其操作方式类似于数据结构中的栈2、堆区(heap) ——般由程序员安排释放,若程序员不释放,程序结束时可能由OS回收。
留意它与数据结构中的堆是两回事,安排方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。
-程序结束后有系统释放4、文字常量区一常量字符串就是放在这里的。
程序结束后由系统释放5、程序代码区一存放函数体的二进制代码。
二、例子程序这是一个前辈写的,特别具体//main, cppint a = 0;全局初始化区char *pl;全局未初始化区main ()(int b;栈char s[] = 〃abc";栈char *p2;栈char *p3 = "123456〃; 123456\0 在常量区,p3 在栈上。
static int c =0;全局(静态)初始化区pl = (char *)malloc(10);p2 = (char *)malloc(20);安排得来得10和20字节的区域就在堆区。
strcpy(pl, 〃123456〃); 123456\0放在常量区,编译器可能会将它与p3 所指向的〃 123456〃优化成一个地方。
}二、堆和栈的理论学问2. 1申请方式stack:由系统自动安排。
例如,声明在函数中一个局部变量int b;系统自动在栈中为b开拓空间heap:需要程序员自己申请,并指明大小,在c中malloc函数如pl=(char*)malloc(10);在 C++中用 new 运算符如 p2 = (char *)malloc (10);但是留意pl、 P2本身是在栈中的2申请后系统的响应栈:只要栈的剩余空间大于所申请空间,系统将为程序供应内存,否则将报特别提示栈溢出。
remobjects pascal script -回复什么是RemObjects Pascal Script, 并介绍其功能特点。
RemObjects Pascal Script 是一个开源的Pascal 脚本语言解释器,旨在为用户提供轻量级的、易于集成的脚本功能。
它可以在多个平台上运行,包括Windows、Linux、Mac OS X 等。
RemObjects Pascal Script 具有多种功能特点,使其成为开发人员们的首选:1. Pascal 语法:作为一种脚本语言,RemObjects Pascal Script 使用Pascal 语法,这是一种易于阅读和理解的高级编程语言。
相较于其他脚本语言,如JavaScript 或Python,Pascal 的语法更接近传统的编程语言,更容易让开发人员们上手。
2. 动态类型:Pascal Script 允许使用动态类型,这意味着变量的类型在运行时才确定。
这样一来,开发人员们可以更加灵活地处理不同类型的数据,无需提前声明变量的类型。
3. 执行效率:RemObjects Pascal Script 的解释器经过优化,具有高效执行的能力。
相较于其他脚本语言,Pascal Script 在执行速度和内存占用方面表现出色,使得其在资源有限的环境中依然表现良好。
4. 多平台支持:RemObjects Pascal Script 可以在多个操作系统上运行,包括Windows、Linux、Mac OS X 等。
这使得开发人员们可以在不同环境下使用相同的脚本代码,提高了代码的可移植性。
5. 集成性:Pascal Script 被设计成易于集成到其他应用程序中。
开发人员们可以通过简单地引入Pascal Script 的解释器和相关的库文件,使其融入到他们的应用程序中。
这样一来,他们可以通过脚本快速添加自定义的功能和扩展,无需重新编译和部署整个应用程序。
6. 强大的功能库:Pascal Script 支持丰富的内置函数和功能库,开发人员们可以利用这些功能来简化代码编写和实现复杂的操作。
Object Pascal的数据类型类型是某类数据的名称,用于确定是否能够存储信息以及能存储多少信息等。
Object pascal 是一种强类型的语言,其数据类型的定义,声明以及数据的赋值和传递都必须追随严格的语言规则。
Object pascal支持丰富的数据类型,大致分为6大类:简单型:包括有序和实数类型,有序:整数、字符、布朗、枚举及字界类型。
字符串类型结构类型:集合、数组、记录、文件、类类型、类引用、接口类型指针类过程类可变类型。
整型:整型数据类型描述了整个数字集合的一个子集。
通用的整型是integer,基本的整型包括shortint、longint和int64。
枚举型:Type<类型名>=<值1、值2、值3...>;字界型:Type<类型名>=起始值...终止值;实型:实数类型是带有小数部分的数值,用于存储实数。
有6种不同的实数类型,实数类型包括Real48、single、double、extended、comp和currency。
通常使用的是real与double类型是等价。
字符串类型:字符串类型包括string(255个字符)、Ansichar(2的31次方)、widechar(2的30次方)这三种类型,string类型中每个元素都是Ansichar类型。
不以null结束标记;Ansichar类型也是基于ansichar类型,可被动态分配存储空间字符几乎都不受限制,以NULL 做结束标记。
结构类型:object pascal结构类型包括,集合、数组、记录、文件、类、类引用、接口类型等。
在delphi中,当用户在窗体中加入一控件,也就是向窗体类中加入了一个域;每个控件也是类,每当用户建立一个事件句柄使得控件可以相应一个事件时,系统会自动地在窗体中加入一个方法。
2.3常量和变量的定义:常量声明的语法格式:Const 常量名=表达式;Eg: constPi=3.14;M=20;St=’I am a student.’;N=m+10;变量是程序代码中表现一个内存地址的标识符,而此地址中的内容在程序代码执行时可以被改变。
详谈C++中虚基类在派⽣类中的内存布局今天重温C++的知识,当看到虚基类这点的时候,那时候也没有太过追究,就是知道虚基类是消除了类继承之间的⼆义性问题⽽已,可是很是好奇,它是怎么消除的,内存布局是怎么分配的呢?于是就深⼊研究了⼀下,具体的原理如下所⽰:在C++中,obj是⼀个类的对象,p是指向obj的指针,该类⾥⾯有个数据成员mem,请问obj.mem和p->mem在实现和效率上有什么不同。
答案是:只有⼀种情况下才有重⼤差异,该情况必须满⾜以下3个条件:(1)、obj 是⼀个虚拟继承的派⽣类的对象(2)、mem是从虚拟基类派⽣下来的成员(3)、p是基类类型的指针当这种情况下,p->mem会⽐obj.mem多了两个中间层。
(也就是说在这种情况下,p->mem⽐obj.mem要明显的慢)WHY?如果好奇⼼⽐较重的话,请往下看 :1、虚基类的使⽤,和为多态⽽实现的虚函数不同,是为了解决多重继承的⼆义性问题。
举例如下:class A{public:int a;};class B : virtual public A{public:int b;};class C :virtual public A{public:int c;};class D : public B, public C{public:int d;};上⾯这种菱形的继承体系中,如果没有virtual继承,那么D中就有两个A的成员int a;继承下来,使⽤的时候,就会有很多⼆义性。
⽽加了virtual继承,在D中就只有A的成员int a;的⼀份拷贝,该拷贝不是来⾃B,也不是来⾃C,⽽是⼀份单独的拷贝,那么,编译器是怎么实现的呢??在回答这个问题之前,先想⼀下,sizeof(A),sizeof(B),sizeof(C),sizeof(D)是多少?(在32位x86的linux2.6下⾯,或者在vc2005下⾯)在linux2.6下⾯,结果如下:sizeof(A) = 4; sizeof(B) = 12; sizeof(C) = 12; sizeof(D) = 24;sizeof(B)为什么是12呢,那是因为多了⼀个指针(这⼀点和虚函数的实现⼀样),那个指针是⼲嘛的呢?那么sizeof(D)为什么是24呢?那是因为除了继承B中的b,C中的c,A中的a,和D⾃⼰的成员d之外,还继承了B,C多出来的2个指针(B和C分别有⼀个)。
Object Pascal参考手册 (Ver 0.1)ezdelphi@Overview Overview(概述)Using object pascal(使用object pascal)Object Pascal是一种高级编译语言,具有强类型(对数据类型的检查非常严格)特性,支持结构化和面向对象编程。
它的优点包括代码的易读性、快速编译,以及支持多个单元文件从而实现模块化编程。
Object Pascal具有一些特性以支持Borland组件框架和RAD(快速应用程序开发)环境。
在很大程度上,本语言参考的说明和示例假定你使用Borland公司的开发工具,如Delphi和Kylix。
绝大多数使用Borland开发工具的开发者是在IDE(集成开发环境)环境下编写代码并进行编译。
Borland开发工具帮助我们设置工程和源文件的许多细节,比如维护单元的依赖信息。
并且,使用这些工具在程序的组织上还有一些限制,严格说来,这不是Object Pascal语言规范的一部分。
比如,Borland开发工具遵循某些文件和程序的命名约定,若你在IDE以外编写代码并使用命令行来编译,你可以避开这些限制。
这些帮助主题假设你在IDE环境下工作,并且使用VCL(可视化组件库)和/或 CLX(跨平台组件库)创建应用程序。
但有时候,Borland一些特定的规则和Object Pascal的通用规则并不相同。
Program organization(程序组织)Program organization: Overview(概述)应用程序通常被分成多个源代码模块,我们称它们为单元(unit)。
每个程序以一个程序头(heading)开始,它为程序指定一个名称。
在程序头之后是一个可选的uses子句,然后是一个由声明和命令语句组成的块(block)。
uses子句列出了那些链接到程序的单元,这些单元可以被不同的程序共享,并且通常有自己的uses子句。
对Object Pascal编译器给类对象分配堆内存细节的一种大胆猜测读过我以前写的文章的网友,都知道我是一个喜欢“刨根问底”、“死钻牛角尖”的家伙。
最近由于工作需要转学DELPHI,在接触Object Pascal之后,果然领会到了它的整洁和优美,怪不得连《程序设计语言:设计与实现》一书的作者也称赞pascal是“一种极优美的语言”。
但在学习过程中遇到了好多问题,特别是对于像我这样由C++转至OP[对Object Pascal的简称]学习的人,由于两种语言风格不同,问号就会更多了。
其中,OP和C++语言的一个很大的区别就是:类对象[或称之为类实例]的内存分配机制不同。
其中有两方面要说:一、什么时候分配?在C++中,定义了对象,那么马上分配其内存,之后调用其构造函数,这个内存可能在堆中,也可能在栈内,也可能在全程数据区内。
但OP却截然不同,定义一对象,如:obj : TObject;只是为其分配了4字节的一个指针空间,而真正的对象空间还没有分配,那怎么用?在使用前当然要给对象分配空间,不然就会造成访问内存出错,给对象分配空间的办法也很简单:obj := Tobject.Create;就OK,这个对象空间是分配在堆内的,大家知道,栈内空间可以在使用期过后自动回收,但堆内存需要程序员自己管理,所以在使用完类对象之后,别忘了 obj.Free [真正实现析构的是obj.Destroy,但obj.free是一种更安全的方式]。
“什么时候分配”这个问题在OP和C++上的答案确实不同,但还不至于让我“疑惑”。
知道了OP类对象是通过调用这样的语句(构造函数):obj := Tobject.Create;来得到堆内存的,但在这个处理细节上,编译器在内部是如何实现分配堆内存的呢?请看下一个问题:二、OP编译器是如何分配的内存?首先要感谢Lippman的《Inside C++ Ojbect Model》,这是一本不可多得的好书,她告诉了你对于C++编译器实现的一些你最迷惑、也是最想关心的细节,但不知DELPHI 业界内有没有这样一本书,可以让我清楚的了解到OP编译器具体[具体到每个细节]是如何给一个类对象分配堆内存的 [如果有这样的书,您一定要通知我:coder@] ?我大胆的做了猜测!一些小动作都是在Tobject类内部事先已经定义好的!下面让我们来关注一下这几个Tobject类方法(Tobject定义于System.pas):TObject = class……constructor Create;procedure Free;class function InitInstance(Instance: Pointer): TObject;procedure CleanupInstance;class function InstanceSize: Longint;class function NewInstance: TObject; virtual;procedure FreeInstance; virtual;destructor Destroy; virtual;end;从方法的名称上我们能隐约的感觉到:NewInstance和FreeInstance肯定和类对象的构造和析构有些关联!先来分析一下NewInstance:class function TObject.NewInstance: TObject;beginResult := InitInstance(_GetMem(InstanceSize));end;只有一句代码,但却调用了三个其它方法:1、class function TObject.InstanceSize: Longint;beginResult := PInteger(Integer(Self) + vmtInstanceSize)^;end;这个方法是OP类实现RTTI的一个重要方法,它能返回类对象所需要占用堆内存的大小,注意它并非是类对象所占有内存大小,因为类对象是一指针,那么在32位环境下,指针永远是4字节!大家可能对这句代码比较疑惑Result := PInteger(Integer(Self) + vmtInstanceSize)^;下面我定义一个OP类:TBase = class(TObject)x : Integer;y : Double;constructor Create;end;然后分配内存:b : Tbase ;b := TBase.Create;我设想分配后的内存布局应是这样的[按C++对象的内存考虑联想的]:再来看这句:Result := PInteger(Integer(Self) + vmtInstanceSize)^;它的目的是取到VMT中Index = -40[注意:常量vmtInstanceSize = -40]的格子中的内容。
大家看这里的Self变量是什么值呢?是b的值也就是VPTR的ADDRESS吗?绝对不是!因为程序在执行到TObject.InstanceSize时只是想通过调用它知道得划分多少堆内存,但还没有正式分配堆内存,也就是说,VPTR、X、Y还不存在[但VMT是和类一同建立起来的,它包含了和类有关的一些信息,如类实例需要请求的堆内存的大小等等],当然这个Self 也就不能是b的值了,我猜测它的内容是VMT中index = 0的格子的Address,只有这样,这里的代码和下面要讲的代码才能被正常解释,但,Self是怎么被Assigned为这个值的,我想是编译器所做的处理吧。
这样,Result := PInteger(Integer(Self) + vmtInstanceSize)^自然得到了类对象所需要堆内存大小的信息!为了证明我上面的猜测是正确的,大家可以实验以下代码:varb :Tbase;size_b : Integer;beginb := TBase.Create;ShowMessage(Format('InitanceSize of TBase : %d',[b.InstanceSize]));size_b := PInteger(PInteger(b)^ - 40)^;ShowMessage(Format('InitanceSize of TBase : %d',[size_b]));……end;大家可以看到,两种方法得到的是同一个值!好,现在我们回过头来讲解TObject.NewInstance中要调用的第二个函数。
2、function _GetMem(Size: Integer): Pointer;它在System.pas 中的定义如下:function _GetMem(Size: Integer): Pointer;{$IF Defined(DEBUG) and Defined(LINUX)}varSignature: PLongInt;{$IFEND}beginif Size > 0 thenbegin{$IF Defined(DEBUG) and Defined(LINUX)}Signature := PLongInt(MemoryManager.GetMem(Size + 4));if Signature = nil thenError(reOutOfMemory);Signature^ := 0;Result := Pointer(LongInt(Signature) + 4);{$ELSE}Result := MemoryManager.GetMem(Size);if Result = nil thenError(reOutOfMemory);{$IFEND}endelseResult := nil;end;具体代码就不分析了,但我们终于看到了OP中分配堆内存的具体函数,原来是OP 是通过一个内存管理器MemoryManager来管理类对象所取得的堆内存空间的!TObject.NewInstance中第三个调用的方法:3、class function TObject.InitInstance(Instance: Pointer): TObject;{$IFDEF PUREPASCAL}varIntfTable: PInterfaceTable;ClassPtr: TClass;I: Integer;beginFillChar(Instance^, InstanceSize, 0);PInteger(Instance)^ := Integer(Self);ClassPtr := Self;while ClassPtr <> nil dobeginIntfTable := ClassPtr.GetInterfaceTable;if IntfTable <> nil thenfor I := 0 to IntfTable.EntryCount-1 dowith IntfTable.Entries[I] dobeginif VTable <> nil thenPInteger(@PChar(Instance)[IOffset])^ := Integer(VTable);end;ClassPtr := ClassPtr.ClassParent;end;Result := Instance;end;{$ELSE}asmPUSH EBXPUSH ESIPUSH EDIMOV EBX,EAXMOV EDI,EDXSTOSDMOV ECX,[EBX].vmtInstanceSize XOR EAX,EAXPUSH ECXSHR ECX,2DEC ECXREP STOSDPOP ECXAND ECX,3REP STOSBMOV EAX,EDXMOV EDX,ESP@@0: MOV ECX,[EBX].vmtIntfTableTEST ECX,ECXJE @@1PUSH ECX@@1: MOV EBX,[EBX].vmtParentTEST EBX,EBXJE @@2MOV EBX,[EBX]JMP @@0@@2: CMP ESP,EDXJE @@5@@3: POP EBXMOV ECX,[EBX].TInterfaceTable.EntryCountADD EBX,4@@4: MOV ESI,[EBX].TInterfaceEntry.VTableTEST ESI,ESIJE @@4aMOV EDI,[EBX].TInterfaceEntry.IOffsetMOV [EAX+EDI],ESI@@4a: ADD EBX,TYPE TInterfaceEntryDEC ECXJNE @@4CMP ESP,EDXJNE @@3@@5: POP EDIPOP ESIPOP EBXend;{$ENDIF}刚才知道_GetMem已经得到了堆内存空间,而我们现在要讨论的这个方法是进行一些必须的初始化。