You are applying a hard type cast on the variable. When you do that, you are telling the compiler that you know what you are doing, and the compiler trusts in you.
Let's ask by question:
What is the address of Member2? Is this a possible Access Violation?
When you assign a value to a member of a class, the class definition of the variable is used by the compiler to calculate the offset ( offset ) of that member in memory, so if you have a class statement like this:
type
TMyClass = class(TObject)
Member1: Integer; //4 bytes
Member2: Integer; //4 bytes
end;
The representation in memory of an instance of this class looks something like this:
referencia (puntero) al objeto
|
|
-------------> [VMT][Member1][Member2][Monitor]
corrimiento 0 4 8 12
When you write a sentence like this:
MyObject.Member2 := 20;
The compiler uses this map of the class to calculate the memory address to which to apply the assignment. In this case, the compiler will translate the assignment in the equivalent to:
PInteger(Cardinal(MyObject) + 8)^ := 20;
So, the assignment you make is successful only because of the way the memory manager default works. An Access Violation originates in the operating system, when you try to access a memory address that has not been assigned to your program. In this case, your program has reserved more than the minimum memory required by the operating system. In my opinion, when you do not get an Access Violation you, in reality, you are not lucky, since the memory of your program has been silently corrupted. Any other variable that could reside in that address will have changed its value (or metadata), and this will result in an indeterminate behavior.
Does the ToString method point to the same address?
Since the ToString()
method is virtual, the address of that method is stored in the virtual methods table ( VMT , and the call is determined at run time. I recommend reading (in English) link and the chapter of the book: The Delphi Object Model .
Why do a and b have the same class name?
At run time, the class name is also part of the metadata of an object. The fact that you are applying the wrong mold to an object does not change the object itself.
Are a and b two different variables?
Of course, if you have declared them yourself, take a look at your code:
var
a: TClassA;
b: TClassB;
Well, they are two different variables. In Delphi, the variables to objects are, in reality, pointers, so after some lines of code, both point to the same direction, but that's something else.
If b is of type TClassA, why can I use the MyToString method on it?
Because you are telling the compiler that this is fine, and as I said before, the compiler trusts you . I would rate this what you have done as dark , but Delphi is also a low level language where you are allowed to do many crazy things, if you want (and you know what you do), but:
Play safe
If you want (and probably want it most of the time) to be on the safe side, do not apply molds like these in your code. Use the operator as (the translation is mine):
The as operator performs verified type molds. The expression
object as class
returns a reference to the same object object , but with the type given by class . At run time, the object must be an instance of the class class , one of its descendants, or nil
. If it is not, an exception is raised. If the type with which object was declared is not related to class - that is, if the types are different and one is not the ancestor of the other - an error occurs at compile time.
So, with the operator as
, you're sure, both at compile time and at run time.
Change your code to something like this:
procedure ShowInstance(A: TClassA);
var
b: TClassB;
begin
b := A as TClassB; //excepción al ejecutarse, el resto del código
//compilado no se ejecutará si a no es de tipo
//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); //éxito
ShowInstance(TClassA.Create); //fallo en tiempo de corrida
//la memoria no se ha corrompido
end;
With information from Delphi Type Casting