Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi Type Casting

I need a clarification on Delphi Type Casting
I wrote an example with two classes: TClassA and TClassB, TClassB derived from TClassA.

the code is as follows:

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.

The program output is:

ToString: a = 5, a = 5
Class Name: a=TClassA, b=TClassA
Address: a=0012FF44, b=0012FF40
My Values is: 5 AND 150

(1) what's the Member2 address? is this a possible "Access Violation"?
(2) ok, ToString() method point to the same address
(3) why a and b have the same ClassName?
(4) ok, a and b are two different variables
(5) if b is TClassA, why can use "MyToString" method?

like image 629
AndreaBoc Avatar asked Jan 31 '13 12:01

AndreaBoc


People also ask

What is an example of type casting?

A typecast example is the transformation of an integer into a string. This could be used to compare two numbers if one is stored as a string and the other is an integer. If compatible, Java must automatically perform a conversion called Automatic Type Conversion and, if not, it must be specifically cast or converted.

What is integer type casting?

Type casting refers to changing an variable of one data type into another. The compiler will automatically change one type of data into another if it makes sense. For instance, if you assign an integer value to a floating-point variable, the compiler will convert the int to a float.

What is type casting what are its types?

Type casting is a way of converting data from one data type to another data type. This process of data conversion is also known as type conversion or type coercion. In Java, we can cast both reference and primitive data types. By using casting, data can not be changed but only the data type is changed.

What is type casting in compiler?

Type casting is the process in which the compiler automatically converts one data type in a program to another one. Type conversion is another name for type casting. For instance, if a programmer wants to store a long variable value into some simple integer in a program, then they can type cast this long into the int.


1 Answers

You're applying a hard type cast over the variable. When you do so, you're telling the compiler you know what you're doing and the compiler trusts you.

(1) what's the Member2 address? is this a possible "Access Violation"?

When you assign a value to a member of the class, the class definition of the variable is used by the compiler to calculate the offset of that member in the memory space, so when you have a class declaration like this:

type
  TMyClass = class(TObject)
    Member1: Integer; //4 bytes
    Member2: Integer; //4 bytes
  end;

the in-memory representation of this object looks like this:

reference (Pointer) to the object
|
|
--------> [VMT][Member1][Member 2][Monitor]
Offset     0    4        8         12

When you issue a statement like this:

MyObject.Member2 := 20;

The compiler just uses that information to calculate the memory address to apply that assignment to. In this case, the compiler the assignment may be translated to

PInteger(NativeUInt(MyObject) + 8)^ := 20;

So, your assignment succeeds just because the way the (default) memory manager works. An AV is originated by the operating system when you try to access a memory address that is not part of your program. In this case, your program has taken more memory from the OS than required. IMHO, when you don't get an AV, you, in fact, are unlucky, because your program memory may now be silently corrupted. Any other variable that happens to reside at that address may have changed its value (or meta-data), and it would result in undefined behavior.

(2) ToString() method point to the same address

Since the ToString() method is a virtual one, the address of that method is stored in the VMT and the call is determined at runtime. Take a look at What data does a TObject contain?, and read the referenced book chapter: The Delphi Object Model.

(3) why a and b have the same ClassName?

The class name is also part of the run-time metadata of the object. The fact that you're applying the wrong mold to the object doesn't change the object itself.

(4) a and b are two different variables

Of course, you declared it, look at your code:

var
  a: TClassA;
  b: TClassB;

Well, two different variables. In Delphi, the object variables are references, so, after some lines of code both reference the same address, but that's a different thing.

(5) if b is TClassA, why can use "MyToString" method?

Because you're telling the compiler that's OK, and as said, the compiler trusts you. It is hacky, but Delphi is also a low level language and you're allowed to do a lot of crazy things, if you want, but:

Play safe

If you want (and you surely want most of the time) to be on the safe side, don't apply a hard cast like that in your code. Use the as operator:

The as operator performs checked typecasts. The expression

object as class

returns a reference to the same object as object, but with the type given by class. At runtime, object must be an instance of the class denoted by class or one of its descendants, or be nil; otherwise an exception is raised. If the declared type of object is unrelated to class - that is, if the types are distinct and one is not an ancestor of the other - a compilation error results.

So, with the as operator, you're safe, both at compile-time and at run-time.

Change your code to:

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;
like image 93
jachguate Avatar answered Nov 15 '22 22:11

jachguate