對(duì)Object Pascal編譯器給類對(duì)象分配堆內(nèi)存細(xì)節(jié)的一種大膽猜測(cè)_第1頁
對(duì)Object Pascal編譯器給類對(duì)象分配堆內(nèi)存細(xì)節(jié)的一種大膽猜測(cè)_第2頁
對(duì)Object Pascal編譯器給類對(duì)象分配堆內(nèi)存細(xì)節(jié)的一種大膽猜測(cè)_第3頁
對(duì)Object Pascal編譯器給類對(duì)象分配堆內(nèi)存細(xì)節(jié)的一種大膽猜測(cè)_第4頁
對(duì)Object Pascal編譯器給類對(duì)象分配堆內(nèi)存細(xì)節(jié)的一種大膽猜測(cè)_第5頁
已閱讀5頁,還剩7頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

1、對(duì)Object Pascal編譯器給類對(duì)象分配堆內(nèi)存細(xì)節(jié)的一種大膽猜測(cè)讀過我以前寫的文章的網(wǎng)友,都知道我是一個(gè)喜歡“刨根問底”、“死鉆牛角尖”的家伙。最近由于工作需要轉(zhuǎn)學(xué)DELPHI,在接觸Object Pascal之后,果然領(lǐng)會(huì)到了它的整潔和優(yōu)美,怪不得連程序設(shè)計(jì)語言:設(shè)計(jì)與實(shí)現(xiàn)一書的作者也稱贊pascal是“一種極優(yōu)美的語言”。但在學(xué)習(xí)過程中遇到了好多問題,特別是對(duì)于像我這樣由C+轉(zhuǎn)至OP對(duì)Object Pascal的簡(jiǎn)稱學(xué)習(xí)的人,由于兩種語言風(fēng)格不同,問號(hào)就會(huì)更多了。其中,OP和C+語言的一個(gè)很大的區(qū)別就是:類對(duì)象或稱之為類實(shí)例的內(nèi)存分配機(jī)制不同。其中有兩方面要說:一、什么時(shí)候分配?在C

2、+中,定義了對(duì)象,那么馬上分配其內(nèi)存,之后調(diào)用其構(gòu)造函數(shù),這個(gè)內(nèi)存可能在堆中,也可能在棧內(nèi),也可能在全程數(shù)據(jù)區(qū)內(nèi)。但OP卻截然不同,定義一對(duì)象,如:obj : TObject;只是為其分配了4字節(jié)的一個(gè)指針空間,而真正的對(duì)象空間還沒有分配,那怎么用?在使用前當(dāng)然要給對(duì)象分配空間,不然就會(huì)造成訪問內(nèi)存出錯(cuò),給對(duì)象分配空間的辦法也很簡(jiǎn)單:obj := Tobject.Create;就OK,這個(gè)對(duì)象空間是分配在堆內(nèi)的,大家知道,棧內(nèi)空間可以在使用期過后自動(dòng)回收,但堆內(nèi)存需要程序員自己管理,所以在使用完類對(duì)象之后,別忘了 obj.Free 真正實(shí)現(xiàn)析構(gòu)的是obj.Destroy,但obj.free是一

3、種更安全的方式。“什么時(shí)候分配”這個(gè)問題在OP和C+上的答案確實(shí)不同,但還不至于讓我“疑惑”。知道了OP類對(duì)象是通過調(diào)用這樣的語句(構(gòu)造函數(shù)):obj := Tobject.Create;來得到堆內(nèi)存的,但在這個(gè)處理細(xì)節(jié)上,編譯器在內(nèi)部是如何實(shí)現(xiàn)分配堆內(nèi)存的呢?請(qǐng)看下一個(gè)問題:二、OP編譯器是如何分配的內(nèi)存?首先要感謝Lippman的Inside C+ Ojbect Model,這是一本不可多得的好書,她告訴了你對(duì)于C+編譯器實(shí)現(xiàn)的一些你最迷惑、也是最想關(guān)心的細(xì)節(jié),但不知DELPHI業(yè)界內(nèi)有沒有這樣一本書,可以讓我清楚的了解到OP編譯器具體具體到每個(gè)細(xì)節(jié)是如何給一個(gè)類對(duì)象分配堆內(nèi)存的 如果有這

4、樣的書,您一定要通知我:coder ?我大膽的做了猜測(cè)!一些小動(dòng)作都是在Tobject類內(nèi)部事先已經(jīng)定義好的!下面讓我們來關(guān)注一下這幾個(gè)Tobject類方法(Tobject定義于System.pas): TObject = class constructor Create; procedure Free; class function InitInstance(Instance: Pointer): TObject; procedure CleanupInstance; class function InstanceSize: Longint; class function NewInstan

5、ce: TObject; virtual; procedure FreeInstance; virtual; destructor Destroy; virtual; end;從方法的名稱上我們能隱約的感覺到:NewInstance和FreeInstance肯定和類對(duì)象的構(gòu)造和析構(gòu)有些關(guān)聯(lián)!先來分析一下NewInstance:class function TObject.NewInstance: TObject;begin Result := InitInstance(_GetMem(InstanceSize);end;只有一句代碼,但卻調(diào)用了三個(gè)其它方法:1、class function T

6、Object.InstanceSize: Longint;begin Result := PInteger(Integer(Self) + vmtInstanceSize);end;這個(gè)方法是OP類實(shí)現(xiàn)RTTI的一個(gè)重要方法,它能返回類對(duì)象所需要占用堆內(nèi)存的大小,注意它并非是類對(duì)象所占有內(nèi)存大小,因?yàn)轭悓?duì)象是一指針,那么在32位環(huán)境下,指針永遠(yuǎn)是4字節(jié)!大家可能對(duì)這句代碼比較疑惑Result := PInteger(Integer(Self) + vmtInstanceSize);下面我定義一個(gè)OP類:TBase = class(TObject) x : Integer; y : Double

7、; constructor Create;end;然后分配內(nèi)存:b : Tbase ; b := TBase.Create;我設(shè)想分配后的內(nèi)存布局應(yīng)是這樣的按C+對(duì)象的內(nèi)存考慮聯(lián)想的:再來看這句:Result := PInteger(Integer(Self) + vmtInstanceSize);它的目的是取到VMT中Index = -40注意:常量vmtInstanceSize = -40的格子中的內(nèi)容。大家看這里的Self變量是什么值呢?是b的值也就是VPTR的ADDRESS嗎?絕對(duì)不是!因?yàn)槌绦蛟趫?zhí)行到TObject.InstanceSize時(shí)只是想通過調(diào)用它知道得劃分多少堆

8、內(nèi)存,但還沒有正式分配堆內(nèi)存,也就是說,VPTR、X、Y還不存在但VMT是和類一同建立起來的,它包含了和類有關(guān)的一些信息,如類實(shí)例需要請(qǐng)求的堆內(nèi)存的大小等等,當(dāng)然這個(gè)Self也就不能是b的值了,我猜測(cè)它的內(nèi)容是VMT中index = 0的格子的Address,只有這樣,這里的代碼和下面要講的代碼才能被正常解釋,但,Self是怎么被Assigned為這個(gè)值的,我想是編譯器所做的處理吧。這樣,Result := PInteger(Integer(Self) + vmtInstanceSize)自然得到了類對(duì)象所需要堆內(nèi)存大小的信息!為了證明我上面的猜測(cè)是正確的,大家可以實(shí)驗(yàn)以下代碼:var b :

9、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;大家可以看到,兩種方法得到的是同一個(gè)值!好,現(xiàn)在我們回過頭來講解TObject.NewInstance中要調(diào)用的第二個(gè)函數(shù)。2、functio

10、n _GetMem(Size: Integer): Pointer;它在System.pas 中的定義如下:function _GetMem(Size: Integer): Pointer;$IF Defined(DEBUG) and Defined(LINUX)var Signature: PLongInt;$IFENDbegin if Size > 0 then begin$IF Defined(DEBUG) and Defined(LINUX) Signature := PLongInt(MemoryManager.GetMem(Size + 4); if Signatu

11、re = nil then Error(reOutOfMemory); Signature := 0; Result := Pointer(LongInt(Signature) + 4);$ELSE Result := MemoryManager.GetMem(Size); if Result = nil then Error(reOutOfMemory);$IFEND end else Result := nil;end;具體代碼就不分析了,但我們終于看到了OP中分配堆內(nèi)存的具體函數(shù),原來是OP是通過一個(gè)內(nèi)存管理器MemoryManager來管理類對(duì)象所取得的堆內(nèi)存空間的!TObject.N

12、ewInstance中第三個(gè)調(diào)用的方法:3、class function TObject.InitInstance(Instance: Pointer): TObject;$IFDEF PUREPASCALvar IntfTable: PInterfaceTable; ClassPtr: TClass; I: Integer;begin FillChar(Instance, InstanceSize, 0); PInteger(Instance) := Integer(Self); ClassPtr := Self; while ClassPtr <> nil do begin I

13、ntfTable := ClassPtr.GetInterfaceTable; if IntfTable <> nil then for I := 0 to IntfTable.EntryCount-1 do with IntfTable.EntriesI do begin if VTable <> nil then PInteger(PChar(Instance)IOffset) := Integer(VTable); end; ClassPtr := ClassPtr.ClassParent; end; Result := Instance;end;$ELSEasm

14、 PUSH EBX PUSH ESI PUSH EDI MOV EBX,EAX MOV EDI,EDX STOSD MOV ECX,EBX.vmtInstanceSize XOR EAX,EAX PUSH ECX SHR ECX,2 DEC ECX REP STOSD POP ECX AND ECX,3 REP STOSB MOV EAX,EDX MOV EDX,ESP0: MOV ECX,EBX.vmtIntfTable TEST ECX,ECX JE 1 PUSH ECX1: MOV EBX,EBX.vmtParent TEST EBX,EBX JE 2 MOV EBX,EBX JMP 0

15、2: CMP ESP,EDX JE 53: POP EBX MOV ECX,EBX.TInterfaceTable.EntryCount ADD EBX,44: MOV ESI,EBX.TInterfaceEntry.VTable TEST ESI,ESI JE 4a MOV EDI,EBX.TInterfaceEntry.IOffset MOV EAX+EDI,ESI4a: ADD EBX,TYPE TInterfaceEntry DEC ECX JNE 4 CMP ESP,EDX JNE 35: POP EDI POP ESI POP EBXend;$ENDIF剛才知道_GetMem已經(jīng)得

16、到了堆內(nèi)存空間,而我們現(xiàn)在要討論的這個(gè)方法是進(jìn)行一些必須的初始化。其它代碼不管,只看這兩句:FillChar(Instance, InstanceSize, 0);PInteger(Instance) := Integer(Self);第一就是給類對(duì)象清零,現(xiàn)在我們知道為什么OP的類實(shí)例的字段會(huì)自動(dòng)被初始化為零了吧String就為空,指針就為nil!第二條語句,是讓VTPR指針指向VMT表的0號(hào)格子讀者請(qǐng)參考結(jié)構(gòu)圖自行分析,此處也證明上面我對(duì)Self值的猜測(cè)的正確性。到了這里,你也許會(huì)說,說了半天,都是猜測(cè),或許,OP編譯器根本就不會(huì)調(diào)用那個(gè)TObject.NewInstance方法呢!問得好

17、,再做實(shí)驗(yàn)!還是以上面的那個(gè)Tbase類為例,重載TObject.NewInstance方法,如下:TBase = class(TObject) x : Integer; y : Double; class function NewInstance: TObject; override; procedure FreeInstance; override; constructor Create; end; 實(shí)現(xiàn)constructor TBase.Create;begin self.x := 2; self.y := 3.14;end; procedure TBase.Free

18、Instance;begin inherited; ShowMessage(Format('Call %s.FreeInstance!',self.ClassName);end; class function TBase.NewInstance: TObject;begin ShowMessage(Format('call %s.NewInstance',self.ClassName); result := inherited NewInstance;end; 之后進(jìn)行簡(jiǎn)單的聲明對(duì)象:varb : Tbase;beginb := Tbase.

19、Create; ß在這里設(shè)斷點(diǎn)!b.Free;end;通過對(duì)代碼進(jìn)行跟蹤果然在一進(jìn)入Create就馬上調(diào)用NewInstance方法。說明:一定要重載它才能跟蹤到它,在斷點(diǎn)處,觀察CPU,從反匯編后的代碼中可以發(fā)現(xiàn),是先調(diào)用一個(gè)_ClassCreate,然后才調(diào)用NewInstance 用同樣的方法可以分析出b.Free會(huì)最終調(diào)用到FreeInstance;來釋放對(duì)象。 我想基本上大的問題已經(jīng)說請(qǐng)了,Object Pascal為了實(shí)現(xiàn)分配堆內(nèi)存,在你調(diào)用構(gòu)造器的時(shí)候:b := Tbase.Create;在構(gòu)造方法內(nèi)你的代碼前,安插了代碼調(diào)用NewInstance方法,析構(gòu)時(shí)

20、,則在析構(gòu)函數(shù)中你的代碼后,調(diào)用FreeInstance函數(shù)。 那么,現(xiàn)在再來看這種情況:派生TBase = class(TObject) x : Integer; y : Double; class function NewInstance: TObject; override; procedure FreeInstance; override; constructor Create; end;  TSub = class (TBase) m : Integer; n : Double; constructor Create; end; 實(shí)現(xiàn)constructo

21、r TBase.Create;begin self.x := 2; self.y := 3.14;end; procedure TBase.FreeInstance;begin inherited; ShowMessage(Format('Call %s.FreeInstance!',self.ClassName); end; class function TBase.NewInstance: TObject;begin ShowMessage(Format('call %s.NewInstance',self.ClassName)

22、; result := inherited NewInstance; end;  TSub  constructor TSub.Create;begin inherited Create; ß注意這里! self.m := 4; self.n := 12.32;end;我們已經(jīng)知道,vars : Tsub; s := Tsub.Create;時(shí),在進(jìn)入Tsub.Create內(nèi)部馬上得到了它想要的內(nèi)存這里是32字節(jié),與內(nèi)存對(duì)齊有關(guān),類里含有最大數(shù)double:8字節(jié),所以總長(zhǎng)度要8的倍數(shù),那么當(dāng):inherited Create;時(shí),在Tbase.Create內(nèi)部,還有內(nèi)存分配的動(dòng)作嗎?我們可以通過三點(diǎn)證明:這里,Tbase.Creat

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論