【编译器优化】且看对象空指针调用对象方法
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.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调用,等会举个例子,看看就更明白了。
[解决办法]
给你写了个例子!
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!