And*_*Boc 8 delphi types casting
我需要澄清一下Delphi Type Casting
我写了两个类的例子:TClassA和TClassB,TClassB派生自TClassA.
代码如下:
program TEST;
{$APPTYPE CONSOLE}
uses
System.SysUtils;
type
TClassA = class(TObject)
public
Member1:Integer;
constructor Create();
function ToString():String; override;
end;
type
TClassB = class(TClassA)
public
Member2:Integer;
constructor Create();
function ToString():String; override;
function MyToString():String;
end;
{ TClassA }
constructor TClassA.Create;
begin
Member1 := 0;
end;
function TClassA.ToString: String;
begin
Result := IntToStr(Member1);
end;
{ TClassB }
constructor TClassB.Create;
begin
Member1 := 0;
Member2 := 10;
end;
function TClassB.MyToString: String;
begin
Result := Format('My Values is: %u AND %u',[Member1,Member2]);
end;
function TClassB.ToString: String;
begin
Result := IntToStr(Member1) + ' - ' + IntToStr(Member2);
end;
procedure ShowInstances();
var
a: TClassA;
b: TClassB;
begin
a := TClassA.Create;
b := TClassB(a); // Casting (B and A point to the same Memory Address)
b.Member1 := 5;
b.Member2 := 150; // why no error? (1)
Writeln(Format('ToString: a = %s, a = %s',[a.ToString,b.ToString])); // (2)
Writeln(Format('Class Name: a=%s, b=%s',[a.ClassName,b.ClassName])); // (3)
Writeln(Format('Address: a=%p, b=%p',[@a,@b])); // (4)
Writeln(b.MyToString); // why no error? (5)
readln;
end;
begin
try
ShowInstances;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Run Code Online (Sandbox Code Playgroud)
程序输出是:
ToString: a = 5, a = 5
Class Name: a=TClassA, b=TClassA
Address: a=0012FF44, b=0012FF40
My Values is: 5 AND 150
Run Code Online (Sandbox Code Playgroud)
(1)会员2的地址是什么?这可能是"访问违规"吗?
(2)ok,ToString()方法指向相同的地址
(3)为什么a和b具有相同的ClassName?
(4)ok,a和b是两个不同的变量
(5)如果b是TClassA,为什么可以使用"MyToString"方法?
jac*_*ate 23
您正在对变量应用硬类型转换.当你这样做时,你告诉编译器你知道你在做什么,编译器信任你.
(1)会员2的地址是什么?这可能是"访问违规"吗?
当您为类的成员分配值时,编译器将使用该变量的类定义来计算该成员在内存空间中的偏移量,因此当您有类声明时,如下所示:
type
TMyClass = class(TObject)
Member1: Integer; //4 bytes
Member2: Integer; //4 bytes
end;
Run Code Online (Sandbox Code Playgroud)
此对象的内存中表示如下所示:
reference (Pointer) to the object
|
|
--------> [VMT][Member1][Member 2][Monitor]
Offset 0 4 8 12
Run Code Online (Sandbox Code Playgroud)
当您发出如下语句时:
MyObject.Member2 := 20;
Run Code Online (Sandbox Code Playgroud)
编译器只使用该信息来计算应用该赋值的内存地址.在这种情况下,编译器可以将赋值转换为
PInteger(NativeUInt(MyObject) + 8)^ := 20;
Run Code Online (Sandbox Code Playgroud)
所以,你的任务成功只是因为(默认)内存管理器的工作方式.当您尝试访问不属于您的程序的内存地址时,操作系统会发起AV.在这种情况下,您的程序从操作系统获取的内存超过了所需的内存.恕我直言,当你没有获得AV时,你实际上是不吉利的,因为你的程序内存现在可能会被默默地破坏.碰巧驻留在该地址的任何其他变量可能已更改其值(或元数据),并且会导致未定义的行为.
(2)ToString()方法指向同一个地址
由于ToString()方法是虚方法,因此该方法的地址存储在VMT中,并且在运行时确定调用.看看TObject包含哪些数据?,并阅读引用的书章:Delphi对象模型.
(3)为什么a和b具有相同的ClassName?
类名也是对象的运行时元数据的一部分.您将错误的模具应用于对象的事实不会改变对象本身.
(4)a和b是两个不同的变量
当然,你宣布它,看看你的代码:
var
a: TClassA;
b: TClassB;
Run Code Online (Sandbox Code Playgroud)
那么,两个不同的变量.在Delphi中,对象变量是引用,因此,在某些代码行之后都引用相同的地址,但这是另一回事.
(5)如果b是TClassA,为什么可以使用"MyToString"方法?
因为你告诉编译器没问题,如上所述,编译器信任你.这很hacky,但是Delphi也是一种低级语言,如果你愿意,你可以做很多疯狂的事情,但是:
如果你想(并且你肯定希望大部分时间都是安全的),不要在你的代码中应用这样的硬强制转换.使用as运算符:
该作为经营者进行检查的类型转换.表达方式
对象 作为 类
返回与对象相同的对象的引用,但是具有类给出的类型.在运行时,对象必须是由类或其后代之一表示的类的实例,或者为nil; 否则会引发异常.如果声明的对象类型与类无关 - 也就是说,如果类型是不同的并且一个不是另一个的祖先 - 则会产生编译错误.
因此,使用as运算符,无论是在编译时还是在运行时,都是安全的.
将您的代码更改为:
procedure ShowInstance(A: TClassA);
var
b: TClassB;
begin
b := A as TClassB; //runtime exception, the rest of the compiled code
//won't be executed if a is not TClassB
b.Member1 := 5;
b.Member2 := 150;
Writeln(Format('ToString: a = %s, a = %s',[a.ToString,b.ToString]));
Writeln(Format('Class Name: a=%s, b=%s',[a.ClassName,b.ClassName]));
Writeln(Format('Address: a=%p, b=%p',[@a,@b]));
Writeln(b.MyToString);
readln;
end;
procedure ShowInstances();
begin
ShowInstance(TClassB.Create); //success
ShowInstance(TClassA.Create); //runtime failure, no memory corrupted.
end;
Run Code Online (Sandbox Code Playgroud)