11

Delphi型キャストについて説明する必要があり
ます。TClassAとTClassB、TClassAから派生したTClassBの2つのクラスで例を作成しました。

コードは次のとおりです。

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.

プログラムの出力は次のとおりです。

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

(1)Member2のアドレスは何ですか?これは「アクセス違反」の可能性がありますか?
(2)わかりました、ToString()メソッドは同じアドレスを指しています
(3)なぜaとbが同じClassNameを持っているのですか?
(4)わかりました、aとbは2つの異なる変数です
(5)bがTClassAの場合、なぜ「MyToString」メソッドを使用できるのですか?

4

3 に答える 3

28

変数にハードタイプキャストを適用しています。そうするとき、あなたはあなたが何をしているのかを知っているとコンパイラーに伝え、コンパイラーはあなたを信頼します。

(1)Member2のアドレスは何ですか?これは「アクセス違反」の可能性がありますか?

クラスのメンバーに値を割り当てると、コンパイラーは変数のクラス定義を使用して、メモリー空間内のそのメンバーのオフセットを計算します。したがって、次のようなクラス宣言がある場合は、次のようになります。

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

このオブジェクトのメモリ内表現は次のようになります。

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

次のようなステートメントを発行すると、次のようになります。

MyObject.Member2 := 20;

コンパイラは、その情報を使用して、その割り当てを適用するメモリアドレスを計算します。この場合、コンパイラの割り当ては次のように変換されます。

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

したがって、(デフォルトの)メモリマネージャが機能するという理由だけで、割り当ては成功します。プログラムの一部ではないメモリアドレスにアクセスしようとすると、AVがオペレーティングシステムによって生成されます。この場合、プログラムはOSから必要以上のメモリを使用しています。私見ですが、AVを入手できない場合、プログラムのメモリがサイレントに破損している可能性があるため、実際には不運です。そのアドレスにたまたま存在する他の変数は、その値(またはメタデータ)を変更した可能性があり、その結果、未定義の動作が発生します。

(2)ToString()メソッドが同じアドレスを指している

ToString()メソッドは仮想メソッドであるため、そのメソッドのアドレスはVMTに格納され、呼び出しは実行時に決定されます。TObjectにはどのようなデータが含まれていますか?、および参照されている本の章を読んでください:Delphiオブジェクトモデル

(3)なぜaとbが同じClassNameを持っているのですか?

クラス名は、オブジェクトの実行時メタデータの一部でもあります。オブジェクトに間違った型を適用しているという事実は、オブジェクト自体を変更しません。

(4)aとbは2つの異なる変数です

もちろん、あなたはそれを宣言しました、あなたのコードを見てください:

var
  a: TClassA;
  b: TClassB;

さて、2つの異なる変数。Delphiでは、オブジェクト変数は参照であるため、コードのいくつかの行の後、両方が同じアドレスを参照しますが、それは別のことです。

(5)bがTClassAの場合、なぜ「MyToString」メソッドを使用できるのですか?

あなたはコンパイラに大丈夫だと言っているので、そして言ったように、コンパイラはあなたを信頼しています。それはハッキーですが、Delphiも低水準言語であり、必要に応じて、多くのクレイジーなことを行うことができますが、次のようになります。

安全にプレイ

安全を確保したい(そしてほとんどの場合は確実に)場合は、コードにそのようなハードキャストを適用しないでください。as演算子を使用します:

as演算子は、チェックされたタイプキャストを実行します。表現

クラスとしての オブジェクト

オブジェクトと同じオブジェクトへの参照を返しますが、クラスで指定されたタイプを使用します。実行時に、オブジェクトはクラスまたはその子孫の1つで示されるクラスのインスタンスであるか、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;
于 2013-01-31T13:26:10.357 に答える
4
  1. Member2メモリマネージャによって割り当てられなかったアドレスがあります。書き込みの結果として考えられるMember2のは、プログラムのまったく異なる部分でのその後のアクセス違反を伴うヒープの破損です。これは非常に厄介なバグであり、コンパイラはここであなたを助けることができません。安全でない型キャストを行うときは、何をしているのかを知っておく必要があります。

  2. これは、ToStringメソッドが仮想であるため、そのアドレスは、作成されるクラスインスタンスの実際のタイプによって決定されるためです。仮想メソッドを静的に置き換えると(この場合、overrideディレクティブをに置き換えるreintroduceと)、結果は異なります。

  3. メソッドも仮想的であるためClassName(実際にはVMTのメンバーではありませんが、実装の詳細は重要ではありません)。

  4. はい、同じインスタンスへの2つの参照ですab

  5. ToMyStringメソッドは静的であるため。インスタンスの実際のタイプは、静的メソッドには関係ありません。

于 2013-01-31T12:53:21.657 に答える
3

(1)Member2のアドレスは何ですか?これは「アクセス違反」の可能性がありますか?

はい。AVが可能です。あなたの場合、あなたは運があります:)

わかりました、ToString()メソッドは同じアドレスを指しています

はい。VTableは作成時に関係します。

(3)なぜaとbが同じClassNameを持っているのですか?

(2)と同じ答え。

(4)ok、a、bは2つの異なる変数です

あまり。スタックからアドレスを印刷しました:)

(5)bがTClassAの場合、なぜ「MyToString」メソッドを使用できるのですか?

bはTClassBですが、誤ってTClassAインスタンスを指しています。

このようなキャストの演算子として使用する必要があります。この場合、失敗します。

于 2013-01-31T12:31:42.470 に答える