读书人

【编译器优化】且看对象空指针调用对象

发布时间: 2012-05-20 16:03:12 作者: rapoo

【编译器优化】且看对象空指针调用对象方法

Delphi(Pascal) code
unit main;interfaceuses  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,  Dialogs, StdCtrls;type  TSimpleTest = class  private  public    procedure PrintClassName;    function DoubleValue(AValue: Double): Double;  end;  TForm1 = class(TForm)    btn1: TButton;    procedure btn1Click(Sender: TObject);  private    { Private declarations }  public    { Public declarations }  end;var  Form1: TForm1;implementation{$R *.dfm}procedure TForm1.btn1Click(Sender: TObject);var  SimpleTest: TSimpleTest;  i: Double;begin  //这里置为nil  SimpleTest := nil;  SimpleTest.PrintClassName;  i := 5;  i := SimpleTest.DoubleValue(I);  ShowMessage(FloatToStr(I));  if not Assigned(SimpleTest) then    ShowMessage('Object is nil!');end;{ TSimpleTest }function TSimpleTest.DoubleValue(AValue: Double): Double;begin  Result := AValue * 2;end;procedure TSimpleTest.PrintClassName;begin  //这里必须用类方法,否则就报错  ShowMessage(TSimpleTest.ClassName);end;end.

各位,上面对象的指针是空的,但是我依然可以调用对象的方法。方法的规律是没有用到对象的数据部分,其实可以写成类方法,但是这里却没有加class标志,但是又可以调用。为什么可以调用呢?我怀疑是被编译器优化了,其实是按照类方法来处理的。这些方法本来就用VMT来管理的。

[解决办法]
不关优化的事!

方法的规律是没有用到对象的数据部分——用到数据部分会出错是因为,会取eax表示的地址的附近的值。而对象的指针是空,意味着eax为0,而不是对象实例的地址,因为权限问题就会报错。
[解决办法]
调用类方法其实就是call一个静态地址,同时隐式传入对象的指针。so,出现了你说的现象。跟优化没有关系。你可以跟踪汇编代码来证实这一点。
[解决办法]
Delphi(Pascal) code
procedure TForm1.btn1Click(Sender: TObject);var  p: Pointer;begin  p := @TSimpleTest.PrintClassName;  asm    MOV EAX, 0    CALL p  end;end;
[解决办法]
这个VMT没多大关系,public中的方法,是不会记录"名称"的,只有地址,任何地方都是CALL这个地址来调用该方法,

类就是record + procedure/function,只不过复杂一点,但最终就是这样
而对象就是个指针 指向new(this record)的内存的指针,

你的例子来说:
function TSimpleTest.DoubleValue(AValue: Double): Double;
begin
Result := AValue * 2;
end;

procedure TSimpleTest.PrintClassName;
begin
//这里必须用类方法,否则就报错
ShowMessage(TSimpleTest.ClassName);
end;

没有用到Self,因此,对NIL对象的调用也没问题,一旦你用self,必然出错,注意直接引用类定义中的数据都是隐含的self调用,等会举个例子,看看就更明白了。
[解决办法]
给你写了个例子!
Delphi(Pascal) code
unit Unit3;interfaceuses  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,  Dialogs;type  TForm3 = class(TForm)    procedure FormCreate(Sender: TObject);  private    { Private declarations }  public    { Public declarations }  end;var  Form3: TForm3;implementation{$R *.dfm}type  Ta = class    f: char;    procedure a;    procedure b;  end;{ Ta }procedure Ta.a;begin  f := 'f';//访问数据成员  ShowMessage('a');end;{procedure Ta.a对应的汇编代码:Unit3.pas.36: f := 'f';0045C028 C6400466         mov byte ptr [eax+$04],$66//eax+$04是数据成员f的地址,此时因为eax=0,所以地址为4,显然是个非法的地址,会报错Unit3.pas.37: ShowMessage('a');0045C02C B840C04500       mov eax,$0045c040//字符串'a'所在的地址0045C031 E82611FDFF       call ShowMessage//调用ShowMessageUnit3.pas.38: end;0045C036 C3               ret//返回}procedure Ta.b;begin  ShowMessage('b');end;{procedure Ta.b对应的汇编代码:Unit3.pas.42: ShowMessage('b');0045C044 B858C04500       mov eax,$0045c058//字符串'b'所在的地址0045C049 E80E11FDFF       call ShowMessage//调用ShowMessageUnit3.pas.43: end;0045C04E C3               ret//返回}procedure TForm3.FormCreate(Sender: TObject);var  o: Ta;begin  o := nil;  o.b;//此行正常执行  o.a;//此行报错end;{procedure TForm3.FormCreate对应的汇编代码:Unit3.pas.48: begin0045C05C 53               push ebx//保存ebxUnit3.pas.49: o := nil;0045C05D 33DB             xor ebx,ebx//清空ebx,这时ebx等于0Unit3.pas.50: o.b;0045C05F 8BC3             mov eax,ebx//将ebx的值赋予eax,这时eax也等于00045C061 E8DEFFFFFF       call Ta.b//调用类Ta的方法bUnit3.pas.51: o.a;0045C066 8BC3             mov eax,ebx//eax等于00045C068 E8BBFFFFFF       call Ta.a//调用类Ta的方法aUnit3.pas.52: end;0045C06D 5B               pop ebx//恢复ebx0045C06E C3               ret//返回}end. 


[解决办法]
13L运行到o.a时,会报错:
'Access violation at address 0045C028 in module 'Project4.exe'. Write of address 00000004'.

其中0045C028正是f := 'f'的地址!其汇编代码为mov byte ptr [eax+$04],$66,而
00000004正是eax+$04!

读书人网 >.NET

热点推荐