一个问题:关于类型转换Type Cast(汇编讲解 as 语法)

问题如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
   TBase = class
 
   end ;
 
   TChild = class (TBase)
   public
     F1: Integer ;
     procedure  Say;
   end ;
 
implementation
 
{$R *.dfm}
 
procedure  TForm3 . FormCreate(Sender: TObject);
var
   A:TBase;
   B:TChild;
begin
   A:=TBase . Create;
   try
     B:=A as  TChild; //编译成功,但是运行报错
     B . Say;
   finally
     A . Free;
   end ;
end ;
 
{ TChild }
 
procedure  TChild . Say;
begin
   ShowMessage( '%D' ,[F1]);
end ;

首先,为什么会报错?编译成功代表语法没有问题.
我们看看As 是怎么实现的?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function  _AsClass(Child: TObject; Parent: TClass): TObject;
{$IFDEF PUREPASCAL}
begin
   Result := Child;
   if  not  (Child is  Parent) then
     Error(reInvalidCast);   // loses return address
end ;
{ $ELSE }
asm
         { ->    EAX     left operand (class)    }
         {       EDX VMT of right operand        }
         { <-    EAX      if left is derived from right, else runtime error      }
         TEST    EAX,EAX //如果对象是Nil就退出
         JE      @@exit
         MOV     ECX,EAX
@@loop:
         MOV     ECX,[ECX] //获取自己的类型
         CMP     ECX,EDX //与要转换的类型进行比较
         JE      @@exit //一样就退出
         MOV     ECX,[ECX].vmtParent //不一样就取父类来比较
         TEST    ECX,ECX //判断父类是否为空.
         JNE     @@loop
 
         {       do runtime error        }
         MOV     AL,reInvalidCast //如果都不能匹配就报错
         JMP     Error
 
@@exit:
end ;
{ $ENDIF }

可以看到As会对类型进行一些列要求,而且是必须的.
如果我们把代码改一下用强制类型转换看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
procedure  TForm3 . FormCreate(Sender: TObject);
var
   A:TBase;
   B:TChild;
begin
   A:=TBase . Create;
   try
     B:=TChild(A); //没有错误
     B . Say; //但是 弹出来的数据非0
   finally
     A . Free;
   end ;
end ;

 

现在运行不会错了,但是弹出来的数据却不是0,为什么?成员变量会初始化的,那么如果创建一个TChild对象的话,
F1就应该是0,不管我们写没有写F1:=0;
要解释这个就需要用到我们之前的知识了.
正常情况下,A的内存:

偏移 0-3 4-7
内容 TBase的地址 $00000000

正常情况下,B的内存:

偏移 0-3 4-7 8-B
内容 TChild地址 $00000000 F1变量的值

也就是F1位于对象地址后面8个字节处.
那么上面的代码中,我们TChild.Say; 会用到F1,而我们把A当成TChild的实例了,是吧?那么去找F1,它就会跑到A+8的地方去,
但是我们看见实际A+8的地方不属于A管,所以这4个字节是未知的,没有被初始化成0,所以报出来也不太可能是0了.
谁叫A是下黑手去抢的内存.

从上面我们可以看到直接强制类型转换速度效率会快一些,所以我们在明白这样转换不会出问题就多用这样转换,但是这中间缺少检验的过程,所以有时候转换后结果可能会错误.
所以我们需要注意了:

1
2
If  Sender Is  TButton then
     TButton(Sender).XXX; //别再用Sender As TButton了,都判断过了

 

好,今天就唠叨到这里,我是DH.

 

你可能感兴趣的:(一个问题:关于类型转换Type Cast(汇编讲解 as 语法))