对Object Pascal编译器给类对象分配堆内存细节的一种大胆猜测(下)

类别:Delphi 点击:0 评论:0 推荐:

到了这里,你也许会说,说了半天,都是猜测,或许,OP编译器根本就不会调用那个TObject.NewInstance方法呢!

问得好,再做实验!

还是以上面的那个Tbase类为例,重载TObject.NewInstance方法,如下:

TBase = class(TObject)

    x : Integer;

    y : Double;

    class function NewInstance: TObject; override;

    procedure FreeInstance; override;

    constructor Create;

  end;

 

{实现}

constructor 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]));

  result := inherited NewInstance;

end;

 

之后进行简单的声明对象:

var

b : Tbase;

begin

b := Tbase.Create;     ß在这里设断点!

b.Free;

end;

通过对代码进行跟踪果然在一进入Create就马上调用NewInstance方法。

[说明:一定要重载它才能跟踪到它,在断点处,观察CPU,从反汇编后的代码中可以发现,是先调用一个_ClassCreate,然后才调用NewInstance]

用同样的方法可以分析出b.Free会最终调用到FreeInstance;来释放对象。

 

我想基本上大的问题已经说请了,Object Pascal为了实现分配堆内存,在你调用构造器的时候:

b := Tbase.Create;

在构造方法内你的代码前,安插了代码调用NewInstance方法,析构时,则在析构函数中你的代码后,调用FreeInstance函数。

 

那么,现在再来看这种情况:派生

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;

 

{实现}

constructor 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]));

  result := inherited NewInstance;

 

end;

 

{ TSub }

 

constructor TSub.Create;

begin

  inherited Create;         ß注意这里!

  self.m := 4;

  self.n := 12.32;

end;

我们已经知道,

var

s : Tsub;

 

s := Tsub.Create;

时,在进入Tsub.Create内部马上得到了它想要的内存[这里是32字节],那么当:

inherited Create;时,在Tbase.Create内部,还有内存分配的动作吗?我们可以通过三点证明:这里,Tbase.Create只是完成程序员给出的初始化代码,没有进行内存分配的动作。

第一点,ReturnValue := inherited Create;所得到的返回地址和调用Tsub.Create所得到的返回地址相同。

第二点,如果在Tbase.Create内部又分配新的内存,那么

self.x := 2;

self.y := 3.14;

只是针对新的内存操作,而原来的S对象中从TBASE中继承来的X,Y不会变,还是0,但我们发现,S中的X,Y已经改变,所以也可以证明Tbase.Create没有分配新的内存,只是对原有内存中的X,Y进行设置。

第三点,跟踪。这是最简单,最一目了然的方法,看看inherited Create;到底有没有调用NewInstance,实验证明,跟本没有调用。

 

但是,如果把Tsub.Create中的inherited Create;改为Tbase.Create;情况则大不同了,用上面三种方式发现,它又分配了新的堆内存,这样不但没有达到程序员初始化数据的目的,反而造成了内存泄漏,而这样的BUG是很难找到的。

也就是说,编译器发现如果是通过类来调用构造函数,就会当成是新的类对象进行构造、分配堆内存,如果是在构造器内部inherited Create;只是按常规的处理 类方法 的方式进行处理。我想,对于Anders Hejlsberg[DELPHI设计者],想在编译器中实现这样的功能并非一件难事[实际上,我们通过查看汇编代码也能分析出个中原由,有兴趣者请注意其中的TEST d1,d1指令和其下的跳转指令]。

 

 

PS:刚才被网友告知有本书叫《delphi的原子世界》,我很想得到它,如果您手上有它的E-BOOK版,希望您能发给我: [email protected]

本文地址:http://com.8s8s.com/it/it5433.htm