Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2021年02月13日] [更新 2021年02月18日] Delphi 一般・その他
変数の値が書き込まれるメモリアドレス
動作確認等 Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32
[整理番号 - 0646]







変数のアドレスと変数のデータが書き込まれるメモリアドレス


プログラムで使用する変数の値 (データ) は全てメモリに書き込まれます。データが書き込まれるメモリ上の位置をアドレスと言います。住所のアドレスと同じような意味でメモリ上の場所を意味します。複数のバイトを使用するデータの場合、複数バイトのメモリ領域 (メモリブロック) を使用します。その先頭のバイト値が書き込まれるアドレスを、データの格納アドレスとかデータが書き込まれているアドレスと言っています。

変数がポインタの時は、その変数のアドレスには実際のデータは書き込まれません。その変数のアドレスには、実際にデータが書き込まれるアドレスの値が書き込まれます。



変数名の前に @ 演算子 (アドレス演算子) を付けると、そのメモリのアドレスが型付きのポインタとして取得できます。型付きポインタからアドレスの値を取得するには、NativeInt または NativeUInt でキャストします。アドレスの値から、メモリに書き込まれているデータを取得するには、そのデータ型のポインタ型の機能を利用します。

本ページでは、それらの処理に次のようなコードを使用しています。


procedure TForm1.Button1Click(Sender: TObject);
var
  LAddress : NativeInt;
  LDouble  : Double;
  LButton  : TButton;
begin
  ....
  LAddress := NativeInt(@LDouble);       // 変数 LDouble のアドレス
                                         // LDouble のデータが格納されているアドレス
  LAddress := NativeInt(Addr(LDouble));  // 型なしポインタを使用する場合

  LDouble  := PDouble(LAddress)^;        // アドレス LAddress にある Double 型のデータ
                                         // ポインタ型の逆参照の機能を利用
  LButton  := TButton(LAddress);         // アドレス LAddress にある TButton 型のデータ
                                         // クラス型の場合は逆参照は不要

end;



[備考 1]
ポインタのサイズは、32 ビットの EXE では 32 ビットです。64 ビットの EXE では 64 ビットです。変数のアドレスの値も 32 ビットの EXE では 32 ビットで、64 ビットの EXE では 64 ビットの整数値になります。そのため、本ページのサンプルは、アドレス値として、プラットフォーム依存の NativeInt 型の変数を使用しています。NativeUInt 型も使用できますが、アドレスは、NativeInt (符号付き整数型) の最大値を超えることはありません。

[備考 2]
変数に @ 演算子を付加すると、それは型付きポインタですが、当サイトのほとんどの記事では単に「変数のアドレス」という表現を使用しています。




01_ 4 バイトの整数型のデータの場所


4 バイト整数の Integer 型の変数のアドレスの取得例でず。Integer 型は 4 バイト使用します。
下図は 3 つの Integer 型の変数のアドレスを、アドレスの値が小さい順に表示しています。変数の宣言順とは逆順になっていますが、アドレスの値が 4 バイト単位で増えていることが確認できます。

数値型が使用するバイト数は SizeOf 関数で取得できます。



図1
Integer 型の変数のアドレスの値
  • Integer 型は 4 バイト使用するので、4 バイト単位で増加している
  • 1635648 + 4 = 1635652
    1635652 + 4 = 1635656
    4 は Integer 型のバイト数

リスト1
procedure TForm1.Button1Click(Sender: TObject);
var
  A : Integer;
  B : Integer;
  C : Integer;
  LA_Address : NativeInt;
  LB_Address : NativeInt;
  LC_Address : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // このアドレスから実際のデータが書き込まれる 
  LA_Address := NativeInt(@A);
  LB_Address := NativeInt(@B);
  LC_Address := NativeInt(@C);

  // アドレスが小さい (低い番地) 順に表示
  Memo1.Lines.Add('C : ' + IntToStr(LC_Address));
  Memo1.Lines.Add('B : ' + IntToStr(LB_Address));
  Memo1.Lines.Add('A : ' + IntToStr(LA_Address));
end;


各々の変数に値を代入して、各々の変数のアドレスにそれらのデータが書き込まれているかを確認した結果が下図です。



図2
変数のアドレスからデータを取得した結果
  • あらかじめ各変数に値を代入しておく

リスト2
procedure TForm1.Button1Click(Sender: TObject);
var
  A : Integer;
  B : Integer;
  C : Integer;
  LA_Address : NativeInt;
  LB_Address : NativeInt;
  LC_Address : NativeInt;
  LA_AdrData : Integer;
  LB_AdrData : Integer;
  LC_AdrData : Integer;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // このアドレスから実際のデータが書き込まれる 
  LA_Address := NativeInt(@A);
  LB_Address := NativeInt(@B);
  LC_Address := NativeInt(@C);


  // 以下はそれらのアドレスに実際にデータが書き込まれるかの確認テスト

  // 各変数に値を代入
  A := 100000;
  B := 200000;
  C := 300000;

  // 各アドレスに書き込まれた値を Integer 型の値として取得
  // 格納されているデータ型のポインタ型でキャストして、逆参照て取得できる
  LA_AdrData := PInteger(LA_Address)^;
  LB_AdrData := PInteger(LB_Address)^;
  LC_AdrData := PInteger(LC_Address)^;

  // 取得した値を表示
  Memo1.Lines.Add(IntToStr(LA_AdrData));
  Memo1.Lines.Add(IntToStr(LB_AdrData));
  Memo1.Lines.Add(IntToStr(LC_AdrData));
end;




02_ 8 バイトの実数型のデータの場所


8 バイト実数の Double 型の変数のアドレスの取得例でず。Doubler 型は 8 バイト使用します。
下図は 3 つの Double 型の変数のアドレスを、アドレスの値が小さい順に表示しています。変数の宣言順とは逆順になっていますが、アドレスの値が 8 バイト単位で増えていることが確認できます。

数値型が使用するバイト数は SizeOf 関数で取得できます。



図3
Double 型の変数のアドレスの値
  • Double 型は 8 バイト使用するので、8 バイト単位で増加している
  • 1635632 + 8 = 1635640
    1635640 + 8 = 1635648
    8 は Double 型のバイト数

リスト3
procedure TForm1.Button1Click(Sender: TObject);
var
  A : Double;
  B : Double;
  C : Double;
  LA_Address : NativeInt;
  LB_Address : NativeInt;
  LC_Address : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // このアドレスから実際のデータが書き込まれる
  LA_Address := NativeInt(@A);
  LB_Address := NativeInt(@B);
  LC_Address := NativeInt(@C);

  // アドレスが小さい (低い番地) 順に表示
  Memo1.Lines.Add('C : ' + IntToStr(LC_Address));
  Memo1.Lines.Add('B : ' + IntToStr(LB_Address));
  Memo1.Lines.Add('A : ' + IntToStr(LA_Address));
end;


各々の変数に値を代入して、各々の変数のアドレスにそれらのデータが書き込まれているかを確認した結果が下図です。



図4
変数のアドレスからデータを取得した結果
  • あらかじめ各変数に値を代入しておく

リスト4
procedure TForm1.Button1Click(Sender: TObject);
var
  A : Double;
  B : Double;
  C : Double;
  LA_Address : NativeInt;
  LB_Address : NativeInt;
  LC_Address : NativeInt;
  LA_AdrData : Double;
  LB_AdrData : Double;
  LC_AdrData : Double;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // これらのアドレスから実際のデータが書き込まれる
  LA_Address := NativeInt(@A);
  LB_Address := NativeInt(@B);
  LC_Address := NativeInt(@C);

  // 以下はそれらのアドレスに実際にデータが書き込まれるかの確認テスト

  // 各変数に値を代入
  A := 1.000E+01;
  B := 2.000E+02;
  C := 3.000E+03;

  // 各アドレスに書き込まれた値を Double 型の値として取得
  // 格納されているデータ型のポインタ型でキャストして、逆参照て取得できる
  LA_AdrData := PDouble(LA_Address)^;
  LB_AdrData := PDouble(LB_Address)^;
  LC_AdrData := PDouble(LC_Address)^;

  // 取得した値を表示
  Memo1.Lines.Add(FormatFloat('0.000E+00', LA_AdrData));
  Memo1.Lines.Add(FormatFloat('0.000E+00', LB_AdrData));
  Memo1.Lines.Add(FormatFloat('0.000E+00', LC_AdrData));
end;




03_ Integer 型の 1 次元の静的配列のデータの場所


Integer 型の 1 次元の静的配列変数のアドレスの取得例でず。配列変数としては 配列の要素数分のメモリが必要です。Integer 型の変数は 4 バイト必要としますから、(4 x 要素数) バイトになります。
下図は 3 つの Integer 型の静的配列変数のアドレスを、アドレスの値が小さい順に表示しています。変数の宣言順とは逆順になっていますが、計算通りに、先頭アドレスの値が増えています。

数値型、あるいは Char 型の静的配列がが使用するバイト数は、SizeOf 関数で取得できます。



図5
Integer 型の静的配列変数のアドレスの値
  • 1635484 + (4 x 10) = 1635524
    1635524 + (4 x 20) = 1635604
    4 は Integer 型が使用するバイト数

リスト5
procedure TForm1.Button1Click(Sender: TObject);
var
  A : array [0..9] of Integer;
  B : array [0..19] of Integer;
  C : array [0..9] of Integer;
  LA_Address : NativeInt;
  LB_Address : NativeInt;
  LC_Address : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // これらはデータの格納開始アドレス
  // これらのアドレスから実際のデータが要素数分書き込まれる
  LA_Address := NativeInt(@A);
  LB_Address := NativeInt(@B);
  LC_Address := NativeInt(@C);

  // アドレスが小さい (低い番地) 順に表示
  Memo1.Lines.Add('C : ' + UIntToStr(LC_Address));
  Memo1.Lines.Add('B : ' + UIntToStr(LB_Address));
  Memo1.Lines.Add('A : ' + UIntToStr(LA_Address));
end;


配列要素に値を代入して、配列要素のメモリアドレスにそれらのデータが書き込まれているかを確認した結果が下図です。



図6
配列要素のアドレスからデータを取得した結果
  • あらかじめ配列要素に値を代入しておく

リスト6
procedure TForm1.Button1Click(Sender: TObject);
var
  LArray   : array [0..2] of Integer;
  LAddress : NativeInt;
  LValue   : Integer;
  LIndex   : Integer;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // このアドレスから実際のデータが要素数分書き込まれる (これは先頭アドレス1)
  LAddress := NativeInt(@LArray);

  LArray[0] := 100;
  LArray[1] := 200;
  LArray[2] := 300;

  // アドレスに書き込まれた値を Integer 型の値として順番に取得
  // 格納されているデータ型のポインタ型でキャストして、逆参照て取得できる
  for LIndex := 0 to Length(LArray) - 1 do begin
    LValue := PInteger(LAddress + SizeOf(Integer) * LIndex)^;
    Memo1.Lines.Add(IntToStr(LValue));
  end;
end;


配列要素のデータが書き込まれているアドレスは、Inc 手続きを使用して計算することもできます。


リスト7
  for LIndex := 0 to Length(LArray) - 1 do begin
    LValue := PInteger(LAddress)^;
    Memo1.Lines.Add(IntToStr(LValue));
    Inc(LAddress, SizeOf(Integer));
    // 以下でも OK
    // Inc(PInteger(LAddress));
  end;


下図は、配列要素に値を代入しないで、配列要素のメモリアドレスからデータを取得した結果です。
データを書き込んでいないのに値が表示されています。これらは、いわゆる「ゴミ」データです。静的配列の要素の値は初期化されないためにこの結果となります。String 型の静的配列の場合も、各要素の値は未定義ですが、Strging 型の変数の未定義状態は何も文字が存在しない状態と同じです。



図7
配列要素のアドレスからデータを取得した結果
  • 配列要素に値が代入されていない場合

リスト8
procedure TForm1.Button1Click(Sender: TObject);
var
  LArray   : array [0..2] of Integer;
  LAddress : NativeInt;
  LValue   : Integer;
  LIndex   : Integer;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // このアドレスから実際のデータが要素数分書き込まれる (これは先頭アドレス1)
  LAddress := NativeInt(@LArray);

  // アドレスに書き込まれた値を Integer 型の値として順番に取得
  // 格納されているデータ型のポインタ型でキャストして、逆参照て取得できる
  for LIndex := 0 to Length(LArray) - 1 do begin
    LValue := PInteger(LAddress + SizeOf(Integer) * LIndex)^;
    Memo1.Lines.Add(IntToStr(LValue));
  end;
end;




04_ Integer 型の 1 次元の動的配列のデータの場所


Integer 型の 1 次元の動的配列変数のアドレスの取得例でず。動的配列はポインタです。
これらの変数のアドレスは、配列要素のデータが書き込まれるアドレスではありません。このアドレスのメモリには、配列要素のデータが書き込まれるアドレスの値が書き込まれています。
下図は 3 つの Integer 型の動的配列変数のアドレスを、アドレスの値が小さい順に表示しています。これらの値は 32 ビットの EXE における値です。32 ビットの EXE のポインタのサイズは 4 バイトです。したがって、変数のアドレスは 4 バイト単位で増加しています。

数値型の動的配列の要素数は、Length 関数で取得できます。



図8
Integer 型の動的配列変数のアドレスの値
  • 32 ビット EXE の場合
  • 1635648 + 4 = 1635652
    1635652 + 4 = 1635656
    4 は 32 ビット EXE のポインタサイズ (バイト)

下図は 64 ビットの EXE における値です。64 ビットの EXE のポインタのサイズは 8 バイトです。したがって、変数のアドレスは 8 バイト単位で増加しています。



図9
Integer 型の動的配列変数のアドレスの値
  • 64 ビット EXE の場合
  • 1240024 + 8 = 1240032
    1240032 + 8 = 1240040
    8 は 64 ビット EXE のポインタサイズ (バイト)

リスト9
procedure TForm1.Button1Click(Sender: TObject);
var
  A : array of Integer;
  B : array of Integer;
  C : array of Integer;
  LA_Address : NativeInt;
  LB_Address : NativeInt;
  LC_Address : NativeInt;
begin
  Memo1.Lines.Clear;

  SetLength(A, 5);
  SetLength(B, 10);
  SetLength(C, 5);

  // 変数のアドレスを取得
  // これらのアドレスは実際のデータが書き込まれるアドレスではない
  // これらの値は、実際のデータがあるアドレスの値
  // つまり、これらの値は実際のデータがある場所を示すポインタとなっている
  LA_Address := NativeInt(@A);
  LB_Address := NativeInt(@B);
  LC_Address := NativeInt(@C);

  // アドレスが小さい (低い番地) 順に表示
  Memo1.Lines.Add('C : ' + IntToStr(LC_Address));
  Memo1.Lines.Add('B : ' + IntToStr(LB_Address));
  Memo1.Lines.Add('A : ' + IntToStr(LA_Address));
end;


配列要素に値を代入して、配列要素のメモリアドレスにそれらのデータが書き込まれているかを確認した結果が下図です。



図10
配列要素のアドレスからデータを取得した結果
  • あらかじめ配列要素に値を代入しておく

リスト10
procedure TForm1.Button1Click(Sender: TObject);
var
  LArray    : array of Integer;
  LAddress  : NativeInt;
  LDataAddr : NativeInt;
  LValue    : Integer;
  LIndex    : Integer;
begin
  Memo1.Lines.Clear;

  SetLength(LArray, 3);

  // 変数のアドレスを取得
  // この値は実際のデータがある場所を示すポインタとなっている
  LAddress := NativeUInt(@LArray);

  LArray[0] := 100;
  LArray[1] := 200;
  LArray[2] := 300;

  // 実際にデータが書き込まれている場所 (最初の要素の先頭アドレス) ょ取得
  // アドレスに書き込まれている値を取得するにはポインタ型を使用する
  LDataAddr := PNativeInt(LAddress)^;

  // 実際のデータは上のアドレスから順番に格納されている
  for LIndex := 0 to Length(LArray) - 1 do begin
    LValue := PInteger(LDataAddr + SizeOf(Integer) * LIndex)^;
    Memo1.Lines.Add(IntToStr(LValue));
  end;
end;


配列要素のデータが書き込まれているアドレスは、Inc 手続きを使用して計算することもできます。


リスト11
  for LIndex := 0 to Length(LArray) - 1 do begin
    LValue := PInteger(LDataAddr)^;
    Memo1.Lines.Add(IntToStr(LValue));
    Inc(LDataAddr, SizeOf(Integer));
    // 以下でも OK
    // Inc(PInteger(LDataAddr));
  end;


下図は、動的配列のサイズ (配列の要素数) を設定していない状態で、配列要素のデータを格納する先頭アドレスを取得した結果ですが、0 になっています。0 と言うことは、データを格納するアドレスが存在しないことになります。この状態で配列要素に対する操作を行うと例外が発生します。
動的配列は、そのサイズを設定することによってデータ格納用のメモリブロックを確保します。



図11
配列サイズが未設定の時のデータ格納用アドレス
  • 0 になる

リスト12
procedure TForm1.Button1Click(Sender: TObject);
var
  LArray    : array of Integer;
  LAddress  : NativeInt;
  LDataAddr : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // この値は実際のデータがある場所を示すポインタとなっている
  LAddress := NativeUInt(@LArray);
  Memo1.Lines.Add('$' + IntToHex(LAddress, SizeOf(NativeInt) * 2));

  // 実際にデータが書き込まれている場所 (最初の要素の先頭アドレス) ょ取得
  // アドレスに書き込まれている値を取得するにはポインタ型を使用する
  LDataAddr := PNativeInt(LAddress)^;
  Memo1.Lines.Add('$' + IntToHex(LDataAddr, SizeOf(NativeInt) * 2));
end;


SetLength 手続きで配列のサイズを設定すると、データの格納アドレスが決まります。SetLength 手続きで配列のサイズを設定すると、要素の値は全て 0 に初期化されます。



図12
配列サイズが設定済みの時のデータ格納用アドレス
  • アドレスが決まる

リスト13
procedure TForm1.Button1Click(Sender: TObject);
var
  LArray    : array of Integer;
  LAddress  : NativeInt;
  LDataAddr : NativeInt;
begin
  Memo1.Lines.Clear;

  SetLength(LArray, 9);

  // 変数のアドレスを取得
  // この値は実際のデータがある場所を示すポインタとなっている
  LAddress := NativeUInt(@LArray);
  Memo1.Lines.Add('$' + IntToHex(LAddress, SizeOf(NativeInt) * 2));

  // 実際にデータが書き込まれている場所 (最初の要素の先頭アドレス) ょ取得
  // アドレスに書き込まれている値を取得するにはポインタ型を使用する
  LDataAddr := PNativeInt(LAddress)^;
  Memo1.Lines.Add('$' + IntToHex(LDataAddr, SizeOf(NativeInt) * 2));
end;




05_ クラス型の変数のデータの場所


クラス型の変数のアドレスの取得例でず。Delphi のクラス型の変数はポインタです。
これらの変数のアドレスは、クラス型の変数のデータが書き込まれているアドレスではありjません。このアドレスのメモリには、クラス型の変数のデータが書き込まれているアドレスの値が書き込まれています。
下図は 3 つの TButton 型の変数のアドレスを、アドレスの値が小さい順に表示しています。これらの値は 32 ビットの EXE における値です。32 ビットの EXE のポインタのサイズは 4 バイトです。したがって、変数のアドレスは 4 バイト単位で増加しています。

クラス型の変数が使用するポインタのバイトサイズは、SizeOf 関数で取得できます。SizeOf 関数の戻り値は、引数がポインタの時はポインタのサイズとなります。



図13
クラス型の変数のアドレスの値
  • 32 ビット EXE の場合
  • 1635648 + 4 = 1635652
    1635652 + 4 = 1635656
    4 は 32 ビット EXE のポインタサイズ (バイト)

下図は 64 ビットの EXE における値です。64 ビットの EXE のポインタのサイズは 8 バイトです。したがって、変数のアドレスは 8 バイト単位で増加しています。



図14
クラス型の変数のアドレスの値
  • 64 ビット EXE の場合
  • 1240024 + 8 = 1240032
    1240032 + 8 = 1240040
    8 は 64 ビット EXE のポインタサイズ (バイト)

リスト14
procedure TForm1.Button1Click(Sender: TObject);
var
  A : TButton;
  B : TButton;
  C : TButton;
  LA_Address : NativeInt;
  LB_Address : NativeInt;
  LC_Address : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // これらのアドレスは実際の TButton のデータが書き込まれるアドレスではない
  // これらの値は、実際の TButton のデータがあるアドレスの値
  // つまり、これらの値は実際の TButton のデータがある場所を示すポインタとなっている
  LA_Address := NativeInt(@A);
  LB_Address := NativeInt(@B);
  LC_Address := NativeInt(@C);

  // アドレスが小さい (低い番地) 順に表示
  Memo1.Lines.Add('C : ' + IntToStr(LC_Address));
  Memo1.Lines.Add('B : ' + IntToStr(LB_Address));
  Memo1.Lines.Add('A : ' + IntToStr(LA_Address));
end;


クラス型のオブジェクトは Create メソッドでそのインスタンス (実体) を生成して操作します。
TButton のオブジェクトと 3 つ生成し、メモリアドレスからそれらの TButton のオフジェクトのデータを取得して確認した結果が下図です。



図15
TButton のデータの格納アドレスからデータを取得

リスト15
procedure TForm1.Button1Click(Sender: TObject);
var
  A : TButton;
  B : TButton;
  C : TButton;
  LA_Address  : NativeInt;
  LB_Address  : NativeInt;
  LC_Address  : NativeInt;
  LA_DataAddr : NativeInt;
  LB_DataAddr : NativeInt;
  LC_DataAddr : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // この値は実際の TButton のプロパティ等がある場所を示すポインタとなっている
  LA_Address := NativeInt(@A);
  LB_Address := NativeInt(@B);
  LC_Address := NativeInt(@C);

  // クラス型の変数は Create してインスタンス (実体[) を生成する
  // インスタンスがないとデータの格納アドレスが決まらない
  A := TButton.Create(Self);
  B := TButton.Create(Self);
  C := TButton.Create(Self);
  try
    A.Parent := Memo1;
    B.Parent := Memo1;
    C.Parent := Memo1;

    A.Left := 100;
    B.Left := 100;
    C.Left := 100;

    A.Top := 10;
    B.Top := 40;
    C.Top := 70;

    A.Caption := 'Btn-10';
    B.Caption := 'Btn-20';
    C.Caption := 'Btn-30';

    // 変数のアドレスに書き込まれている値を取得
    // その値が実際に TButton のプロパティ等が書き込まれている先頭アドレス
    LA_DataAddr := PNativeInt(LA_Address)^;
    LB_DataAddr := PNativeInt(LB_Address)^;
    LC_DataAddr := PNativeInt(LC_Address)^;

    Memo1.Lines.Add(TButton(LA_DataAddr).Caption);
    Memo1.Lines.Add(TButton(LB_DataAddr).Caption);
    Memo1.Lines.Add(TButton(LC_DataAddr).Caption);
  finally
//    Owner も Parent も指定しているので自動解放してくれる
//    FreeAndNil(A);
//    FreeAndNil(B);
//    FreeAndNil(C);
  end;
end;


下図は、TButton のインスタンスを生成しない場合、ポインタが示すアドレスを調べた結果です。
このアドレスには、TButton のデータが書き込まれているアドレスの値が書き込まれますが、下図の値は、いわゆる「ゴミ」データです。
この状態でオブジェクトに対する操作を行うと例外が発生します。



図16
クラス型の変数のインスタンスが生成されていない時のデータ格納用アドレス
  • 結果の値は「ゴミ」データ
  • これはクラス型の変数がローカル変数の場合

リスト16
procedure TForm1.Button1Click(Sender: TObject);
var
  LButton   : TButton;
  LAddress  : NativeInt;
  LDataAddr : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // この値は実際の TButton のデータがある場所を示すポインタとなっている
  LAddress := NativeUInt(@LButton);
  Memo1.Lines.Add('$' + IntToHex(LAddress, SizeOf(NativeInt) * 2));

  // 実際にデータが書き込まれている場所ょ取得
  // アドレスに書き込まれている値を取得するにはポインタ型を使用する
  LDataAddr := PNativeInt(LAddress)^;
  Memo1.Lines.Add('$' + IntToHex(LDataAddr, SizeOf(NativeInt) * 2));
end;


上の結果は、クラス型の変数がローカル変数の場合です。グローバル変数の場合は、下図のように 0 になります。0 と言うことは、データを格納するアドレス (メモリ上の場所) が存在しないことになります。この状態でオブジェクトに対する操作を行うと例外が発生します。
この状態は、オブジェクトが nil の状態です。クラス型のローカル変数は初期化されませんが、グロール変数は nil に初期化されます。



図17
クラス型の変数のインスタンスが生成されていない時のデータ格納用アドレス
  • 0 となる
  • つまり、データを格納する場所は存在しない
  • これはクラス型の変数がグローバル変数の場合

リスト17
var
  FButton : TButton;

procedure TForm1.Button1Click(Sender: TObject);
var
  LAddress  : NativeInt;
  LDataAddr : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // この値は実際の TButton のデータがある場所を示すポインタとなっている
  LAddress := NativeUInt(@FButton);
  Memo1.Lines.Add('$' + IntToHex(LAddress, SizeOf(NativeInt) * 2));

  // 実際にデータが書き込まれている場所ょ取得
  // アドレスに書き込まれている値を取得するにはポインタ型を使用する
  LDataAddr := PNativeInt(LAddress)^;
  Memo1.Lines.Add('$' + IntToHex(LDataAddr, SizeOf(NativeInt) * 2));
end;


Create メソッドで、対象のクラス型変数のインスタンスを生成すると、データ格納用のメモリアドレスが決まります。



図18
クラス型の変数のインスタンス生成後
  • このアドレスには実際のデータが存在する

リスト18
procedure TForm1.Button1Click(Sender: TObject);
var
  LButton   : TButton;
  LAddress  : NativeInt;
  LDataAddr : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // この値は実際の TButton のデータがある場所を示すポインタとなっている
  LAddress := NativeUInt(@LButton);
  Memo1.Lines.Add('$' + IntToHex(LAddress, SizeOf(NativeInt) * 2));

  LButton := TButton.Create(Self);
  try
    // 実際にデータが書き込まれている場所ょ取得
    // アドレスに書き込まれている値を取得するにはポインタ型を使用する
    LDataAddr := PNativeInt(LAddress)^;
    Memo1.Lines.Add('$' + IntToHex(LDataAddr, SizeOf(NativeInt) * 2));
  finally
    FreeAndNil(LButton);
  end;
end;


[備考]
クラス型の変数が使用するバイトサイズは、変数のデータを TMemoryStream.WriteComponent メソッドでメモリストリームに書き込めば、そのメモリストリームのサイズで知ることができます。このサイズは、プロパティ等の設定内容によって変化します。TBitmap 等、SaveTostream メソッドが実装されていれば、そのストリームのサイズで知ることができます。




06_ String 型の変数のデータの場所


String 型の変数のアドレスの取得例でず。Delphi の String 型の変数はポインタです。
これらの変数のアドレスは、String 型の変数のデータが書き込まれているアドレスではありjません。このアドレスのメモリには、String 型の文字列データが書き込まれているアドレスの値が書き込まれています。
下図は 3 つの String 型の変数のアドレスを、アドレスの値が小さい順に表示しています。これらの値は 32 ビットの EXE における値です。32 ビットの EXE のポインタのサイズは 4 バイトです。したがって、変数のアドレスは 4 バイト単位で増加しています。

String 型の変数が使用するバイトサイズは、Length(変数名) x SizeOf(Char) で取得できます。



図19
String 型の変数のアドレスの値
  • 32 ビット EXE の場合
  • 1635648 + 4 = 1635652
    1635652 + 4 = 1635656
    4 は 32 ビット EXE のポインタサイズ (バイト)

リスト19
procedure TForm1.Button1Click(Sender: TObject);
var
  A : string;
  B : string;
  C : string;
  LA_Address : NativeInt;
  LB_Address : NativeInt;
  LC_Address : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // これらのアドレスは String 型の文字列が書き込まれるアドレスではない
  // このアドレスには文字列のデータを書き込むアドレスが格納されている
  LA_Address := NativeInt(@A);
  LB_Address := NativeInt(@B);
  LC_Address := NativeInt(@C);

  // アドレスが小さい (低い番地) 順に表示
  Memo1.Lines.Add('C : ' + IntToStr(LC_Address));
  Memo1.Lines.Add('B : ' + IntToStr(LB_Address));
  Memo1.Lines.Add('A : ' + IntToStr(LA_Address));
end;


String 型の文字列データを格納するメモリアドレスは、String 型の変数に代入する文字列が変わると変わります。そのことを確認するテストです。



図20
文字列のデータが書き込まれるアドレス
  • 文字列が変わると変わる

リスト20
procedure TForm1.Button1Click(Sender: TObject);
var
  LText    : string;
  LGetText : string;
  LAdrVar  : NativeInt;
  LAdrData : NativeInt;
begin
  Memo1.Lines.Clear;

  LText := '日本語';

  // 変数のアドレスを取得
  // このアドレスは String 型の文字列が書き込まれるアドレスではない
  // このアドレスには文字列のデータを書き込むアドレスが格納されている
  LAdrVar := NativeInt(@LText);
  Memo1.Lines.Add('$' + IntToHex(LAdrVar, SizeOf(NativeInt) * 2));

  // LText のデータの格納アドレスを取得して表示
  LAdrData := PNativeInt(LAdrVar)^;
  Memo1.Lines.Add('$' + IntToHex(LAdrData, SizeOf(NativeInt) * 2));

  // そのアドレスに書き込まれている文字列を取得して表示
  LGetText := String(LAdrData);
  Memo1.Lines.Add(LGetText);

  Memo1.Lines.Add('');

  // LText に別の文字列を代入
  LText := '別の文字列';

  // LText のデータの格納アドレスを取得して表示
  LAdrData := PNativeInt(LAdrVar)^;
  Memo1.Lines.Add('$' + IntToHex(LAdrData, SizeOf(NativeInt) * 2));

  // そのアドレスに書き込まれている文字列を取得して表示
  LGetText := String(LAdrData);
  Memo1.Lines.Add(LGetText);
end;


String 型の変数を宣言しただけの状態の場合、文字列データの格納アドレスの値は 0 となります。これは何も文字列が存在しないことを意味します。変数に空文字を代入した場合も同じです。
つまり、String 型の変数を宣言すると、空文字で初期化されます。



図21
変数に文字列を代入していない場合
  • 文字列データの格納アドレスの値は 0
  • 変数に空文字を代入した場合も同じ

リスト21
procedure TForm1.Button1Click(Sender: TObject);
var
  LText    : string;
  LAdrVar  : NativeInt;
  LAdrData : NativeUInt;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // このアドレスは String 型の文字列が書き込まれるアドレスではない
  // このアドレスには文字列のデータを書き込むアドレスが格納されている
  LAdrVar := NativeInt(@LText);
  Memo1.Lines.Add('$' + IntToHex(LAdrVar, SizeOf(NativeInt) * 2));

  // 上で取得したアドレスに書き込まれている値を取得
  // この値が実際に文字列が書き込まれるアドレスの値
  LAdrData := PNativeInt(LAdrVar)^;
  Memo1.Lines.Add('$' + IntToHex(LAdrData, SizeOf(NativeInt) * 2));
end;




07_ PChar 型の変数のデータの場所


PChar 型の変数のアドレスの取得例でず。Delphi の PChar 型は、Null 終端文字列のポイント型です。ポイント型の変数はポインタです。
これらの変数のアドレスは、PChar 型の変数のデータが書き込まれているアドレスではありjません。このアドレスのメモリには、PChar 型の文字列データが書き込まれているアドレスの値が書き込まれています。
下図は 3 つの PChar 型の変数のアドレスを、アドレスの値が小さい順に表示しています。これらの値は 32 ビットの EXE における値です。32 ビットの EXE のポインタのサイズは 4 バイトです。したがって、変数のアドレスは 4 バイト単位で増加しています。

PChar 型の変数が使用するバイトサイズは、StrLen(変数名) x SizeOf(Char) で取得できます。StrLen 関数の戻り値には Null 終端文字分は含まれません。



図22
PChar 型の変数のアドレスの値
  • 32 ビット EXE の場合
  • 1635648 + 4 = 1635652
    1635652 + 4 = 1635656
    4 は 32 ビット EXE のポインタサイズ (バイト)

リスト22
procedure TForm1.Button1Click(Sender: TObject);
var
  A : PChar;
  B : PChar;
  C : PChar;
  LA_Address : NativeInt;
  LB_Address : NativeInt;
  LC_Address : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // これらのアドレスは PChar 型の文字列が書き込まれるアドレスではない
  // このアドレスには文字列のデータを書き込むアドレスが格納されている
  LA_Address := NativeInt(@A);
  LB_Address := NativeInt(@B);
  LC_Address := NativeInt(@C);

  // アドレスが小さい順に表示
  Memo1.Lines.Add('C : ' + IntToStr(LC_Address));
  Memo1.Lines.Add('B : ' + IntToStr(LB_Address));
  Memo1.Lines.Add('A : ' + IntToStr(LA_Address));
end;


PChar 型の文字列データを格納するメモリアドレスは、PChar 型の変数に代入する文字列が変わると変わります。そのことを確認するテストです。



図23
文字列のデータが書き込まれるアドレス
  • 文字列が変わると変わる

リスト23
procedure TForm1.Button1Click(Sender: TObject);
var
  LPChar   : PChar;
  LAdrVar  : NativeInt;
  LAdrData : NativeInt;
begin
  Memo1.Lines.Clear;

  LPChar := 'PChar 日本語';

  // 変数 LPChar のアドレスを取得
  // このアドレスは PChar 型の文字列が書き込まれるアドレスではない
  // このアドレスには文字列のデータを書き込むアドレスが格納されている
  LAdrVar := NativeInt(@LPChar);
  Memo1.Lines.Add('$' + IntToHex(LAdrVar, SizeOf(NativeInt) * 2));

  // LPChar の文字列データの格納アドレスを取得して表示
  LAdrData := PNativeInt(LAdrVar)^;
  Memo1.Lines.Add('$' + IntToHex(LAdrData, SizeOf(NativeInt) * 2));

  // そのアドレスに書き込まれている文字列を取得して表示
  Memo1.Lines.Add(PChar(LAdrData));

  Memo1.Lines.Add('');

  // LPChar に別の文字列を代入
  LPChar := 'PChar 別の文字列';

  // LPChar の文字列データの格納アドレスを取得して表示
  LAdrData := PNativeInt(LAdrVar)^;
  Memo1.Lines.Add('$' + IntToHex(LAdrData, SizeOf(NativeInt) * 2));

  // そのアドレスに書き込まれている文字列を取得して表示
  Memo1.Lines.Add(PChar(LAdrData));
end;


PChar 型の文字列変数をローカル変数として宣言した場合、ポインタが示すアドレスは 0 になりませんが、このアドレスkの値はいわゆる「ゴミ」です。この状態で PChar 型の変数の文字列を取得しても、必ずしも例外が発生するわけではありまんが、文字化けします。
PChar 型のグローバル変数は nil に初期化されますが、ローカル変数は初期化されません。



図24
PChar 型の変数を宣言した直後の状態
  • ローカル変数の場合
  • 初期化はされない

リスト24
procedure TForm1.Button1Click(Sender: TObject);
var
  LPChar   : PChar;
  LAdrVar  : NativeInt;
  LAdrData : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数 LPChar のアドレスを取得
  // このアドレスは PChar 型の文字列が書き込まれるアドレスではない
  // このアドレスには文字列のデータを書き込むアドレスが格納されている
  LAdrVar := NativeInt(@LPChar);
  Memo1.Lines.Add('$' + IntToHex(LAdrVar, SizeOf(NativeInt) * 2));

  // LPChar のデータの格納アドレスを取得して表示
  LAdrData := PNativeInt(LAdrVar)^;
  Memo1.Lines.Add('$' + IntToHex(LAdrData, SizeOf(NativeInt) * 2));

  // 文字列を表示してみる
  Memo1.Lines.Add(LPChar);
end;


PChar 型の文字列変数をグローバル変数として宣言した場合、ポインタが示すアドレスを調べた結果です。 このアドレスに、PChar 型の変数の文字列データが書き込まれますが、0 にまっています。この状態は文字列としてのデータが存在しない状態で、PChar 型の変数は nil となっています。
PChar 型のローカル変数は初期化されませんが、グローバル変数は nil に初期化されます。



図25
PChar 型の変数を宣言した直後の状態
  • グローバル変数の場合
  • nil に初期化される

リスト25
var
  FPChar : PChar;

procedure TForm1.Button1Click(Sender: TObject);
var
  LAdrVar  : NativeInt;
  LAdrData : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数 FPChar のアドレスを取得
  // このアドレスは PChar 型の文字列が書き込まれるアドレスではない
  // このアドレスには文字列のデータを書き込むアドレスが格納されている
  LAdrVar := NativeInt(@FPChar);
  Memo1.Lines.Add('$' + IntToHex(LAdrVar, SizeOf(NativeInt) * 2));

  // FPChar のデータの格納アドレスを取得して表示
  LAdrData := PNativeInt(LAdrVar)^;
  Memo1.Lines.Add('$' + IntToHex(LAdrData, SizeOf(NativeInt) * 2));
end;


Windows API の関数で PChar 型の引数を使用する際、PChar 型の文字列用のメモリを確保しなければならない場合があります。AllocMem 関数等でメモリを確保すると、PChar 型の文字列データを格納するメモリアドレスが決まります。
確保したメモリブロックのサイズを超える長さの文字列は扱えません。確保したメモリは解放処理が必要です。 確保したメモリを解放しても、PChar 型の変数は nil にはなりません。明示的に nil にするには、 PChar 型の変数に nil を代入します。



図26
PChar 型の変数用のメモリを確保した場合
  • 文字列データの格納アドレスが決まる

リスト26
var
  FPChar : PChar;

procedure TForm1.Button1Click(Sender: TObject);
var
  LAdrVar  : NativeInt;
  LAdrData : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数 FPChar のアドレスを取得
  // このアドレスは PChar 型の文字列が書き込まれるアドレスではない
  // このアドレスには文字列のデータを書き込むアドレスが格納されている
  LAdrVar := NativeInt(@FPChar);
  Memo1.Lines.Add('$' + IntToHex(LAdrVar, SizeOf(NativeInt) * 2));

  // PChar 変数用のメモリを確保 (バイト単位)
  try
    FPChar := AllocMem(100);
    // FPChar のデータの格納アドレスを取得して表示
    LAdrData := PNativeInt(LAdrVar)^;
    Memo1.Lines.Add('$' + IntToHex(LAdrData, SizeOf(NativeInt) * 2));
  finally
    // 確保したメモリを解放
    FreeMem(FPChar);
  end;
end;




08_ レコード型 TRect の変数のデータの場所


レコード型 TRect の変数のアドレスの取得例でず。レコード型の型名にはプリフィックス T がありますが、ポインタではありません。変数のアドレスにデータが書き込まれます。レコード型のメンバに動的配列、String 型、PChar 型、あるいはポインタ型のメンバがある場合、それらは、これまでのサンプルのような構造となります。
TRect は (フィールドとしての) メンバが 4 つあり、全て Integer 型です。Integer 型は 4 バイト使用しますから、4 x 4 = 16 バイトのメモリを使用します。

レコード型の変数が使用するバイト数は、SizeOf 関数で取得できます。



図27
レコード型 TRect の変数のアドレスの値
  • 1635596 + 16 = 1635612
    1635612 + 16 = 1635628
    16 はレコード型の変数が使用するバイト数

リスト27
procedure TForm1.Button1Click(Sender: TObject);
var
  A : TRect;
  B : TRect;
  C : TRect;
  LA_Address : NativeInt;
  LB_Address : NativeInt;
  LC_Address : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数の値が書き込まれるアドレスを取得
  LA_Address := NativeInt(@A);
  LB_Address := NativeInt(@B);
  LC_Address := NativeInt(@C);

  // アドレスが小さい (低い番地) 順に表示
  Memo1.Lines.Add('C : ' + IntToStr(LC_Address));
  Memo1.Lines.Add('B : ' + IntToStr(LB_Address));
  Memo1.Lines.Add('A : ' + IntToStr(LA_Address));
end;


TRect の各メンバに値を代入して、各々のメンバの値をメモリアドレスから取得した結果です。



図28
変数のアドレスからデータを取得した結果
  • TRect 型の変数に代入した値が取得できる

リスト28
procedure TForm1.Button1Click(Sender: TObject);
var
  LAddress : NativeInt;
  LRect    : TRect;
  LDstRect : TRect;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // このアドレスから実際のデータが要素数分書き込まれる (これは先頭アドレス1)
  LAddress := NativeInt(@LRect);

  SetRect(LRect, 100, 200, 350, 450);

  // アドレスに書き込まれた値を TRect 型の値として取得
  // 格納されているデータ型のポインタ型の逆参照て取得できる
  LDstRect := PRect(LAddress)^;

  Memo1.Lines.Add(IntToStr(LDstRect.Left));
  Memo1.Lines.Add(IntToStr(LDstRect.Top));
  Memo1.Lines.Add(IntToStr(LDstRect.Right));
  Memo1.Lines.Add(IntToStr(LDstRect.Bottom));
end;


下図は、レコード型 TRect の変数宣言だけで、各メンバに値を代入しないで、メモリアドレスから各メンバの値を取得した結果です。これらの値は「ゴミ」データです。グローバル変数にすれば各メンバの値は初期化されて、全て 0 になります。



図29
レコード型 TRect の初期値
  • ローカル変数の場合は初期化されない
  • グローバル変数であれば 0 に初期化される

リスト29
procedure TForm1.Button1Click(Sender: TObject);
var
  LAddress : NativeInt;
  LRect    : TRect;
  LDstRect : TRect;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // このアドレスから実際のデータが要素数分書き込まれる (これは先頭アドレス1)
  LAddress := NativeInt(@LRect);

  // アドレスに書き込まれた値を TRect 型の値として取得
  // 格納されているデータ型のポインタ型の逆参照て取得できる
  LDstRect := PRect(LAddress)^;

  Memo1.Lines.Add(IntToStr(LDstRect.Left));
  Memo1.Lines.Add(IntToStr(LDstRect.Top));
  Memo1.Lines.Add(IntToStr(LDstRect.Right));
  Memo1.Lines.Add(IntToStr(LDstRect.Bottom));
end;




09_ ポインタ型 PDouble 型の変数のデータの場所


ポインタ型の変数のアドレスの取得例でず。ポインタ型のデータの格納方式は、クラス型の変数のデータの格納方式と同じです。
これらの変数のアドレスは、実際のデータが書き込まれているアドレスではありjません。このアドレスのメモリには、実際のデータが書き込まれているアドレスの値が書き込まれています。
下図は 3 つの PDouble 型の変数のアドレスを、アドレスの値が小さい順に表示しています。これらの値は 32 ビットの EXE における値です。32 ビットの EXE のポインタのサイズは 4 バイトです。したがって、変数のアドレスは 4 バイト単位で増加しています。

ポインタ型の変数が使用するバイトサイズは、SizeOf 関数でも取得できます。



図30
ポインタ型の変数のアドレスの値
  • 32 ビット EXE の場合
  • 1635648 + 4 = 1635652
    1635652 + 4 = 1635656
    4 は 32 ビット EXE のポインタサイズ

リスト30
procedure TForm1.Button1Click(Sender: TObject);
var
  A : PDouble;
  B : PDouble;
  C : PDouble;
  LA_Address : NativeInt;
  LB_Address : NativeInt;
  LC_Address : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // これらのアドレスは Double 型の値書き込まれるアドレスではない
  // このアドレスにはデータを書き込むアドレスの値が格納されている
  LA_Address := NativeInt(@A);
  LB_Address := NativeInt(@B);
  LC_Address := NativeInt(@C);

  // 取得した値を表示
  Memo1.Lines.Add('C : ' + IntToStr(LC_Address));
  Memo1.Lines.Add('B : ' + IntToStr(LB_Address));
  Memo1.Lines.Add('A : ' + IntToStr(LA_Address));
end;


ポインタ型の変数は、New 関数でポインタサイズ分のメモリを確保してから操作します。確保したメモリは解放処理が必要です。PDouble 型のメモリを確保して、メモリアドレスから Double 型のデータを取得した結果が下図です。ポインタ型の逆参照の機能を利用しています。



図31
ポインタ型のデータの格納アドレスからデータを取得

リスト31
procedure TForm1.Button1Click(Sender: TObject);
var
  LPDouble : PDouble;
  LAddress : NativeInt;
  LAdrData : NativeInt;
  LDstDbl  : Double;
begin
  Memo1.Lines.Clear;

  // ポインタのサイズ分のメモリを確保
  New(LPDouble);
  try
    // 変数のアドレスを取得
    // このアドレスにはデータを書き込むアドレスの値が格納されている
    LAddress := NativeInt(@LPDouble);

    LPDouble^ := 1.23456E+15;

    // 実際にデータを書き込まれているアドレスを取得
    LAdrData := PNativeInt(LAddress)^;
    Memo1.Lines.Add('$' + IntToHex(LAdrData, SizeOf(Pointer) * 2));

    // 書き込まれているデータを Double 型の値として取得
    // ポインタ型の逆参照の機能を利用 
    LDstDbl := PDouble(LAdrData)^;
    Memo1.Lines.Add(FormatFloat('0.0000000E+00', LDstDbl));

    Memo1.Lines.Add('');

    // 別の値で確認
    LPDouble^ := 5.4321E+12;

    LAdrData := PNativeInt(LAddress)^;
    Memo1.Lines.Add('$' + IntToHex(LAdrData, SizeOf(Pointer) * 2));

    LDstDbl := PDouble(LAdrData)^;
    Memo1.Lines.Add(FormatFloat('0.0000000E+00', LDstDbl));
  finally
    Dispose(LPDouble);
  end;
end;


ポインタとしてのメモリを確保しないでポインタが示すアドレスを調べた結果です。
このアドレスには、実際のデータが書き込まれているアドレスの値が書き込まれますが、下図の値は、いわゆる「ゴミ」データです。
この状態でポインタ型の変数に対して操作しても、必ずしも例外が発生するわけではありませんが、他の処理で問題が発生する可能性があります。



図32
ポインタ型のメモリを確保していない時のデータ格納用アドレス
  • 結果の値は「ゴミ」データ
  • ポインタ型の変数がローカル変数の場合

リスト32
procedure TForm1.Button1Click(Sender: TObject);
var
  LPDouble : PDouble;
  LAddress : NativeInt;
  LAdrData : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // このアドレスにはデータを書き込むアドレスの値が格納されている
  LAddress := NativeInt(@LPDouble);
  Memo1.Lines.Add('$' + IntToHex(LAddress, SizeOf(Pointer) * 2));

  // 実際にデータを書き込むアドレスを取得
  LAdrData := PNativeInt(LAddress)^;
  Memo1.Lines.Add('$' + IntToHex(LAdrData, SizeOf(Pointer) * 2));
end;


上の結果は、ポインタ型の変数がローカル変数の場合です。グローバル変数の場合は、下図のように 0 になります。0 と言うことは、データを格納するアドレス (メモリ上の場所) が存在しないことになります。この状態は、ポインタが nil の状態です。ポインタ型のローカル変数は初期化されませんが、グロール変数は nil に初期化されます。
この状態でポインタ型の変数に対して逆参照の操作を行うと例外が発生します。



図33
ポインタ型のメモリを確保していない時のデータ格納用アドレス
  • 0 となる (nil 状態)
  • つまり、データを格納する場所は存在しない
  • ポインタ型の変数がグローバル変数の場合

リスト33
var
  LPDouble : PDouble;

procedure TForm1.Button1Click(Sender: TObject);
var
  LAddress : NativeInt;
  LAdrData : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // このアドレスにはデータを書き込むアドレスの値が格納されている
  LAddress := NativeInt(@LPDouble);
  Memo1.Lines.Add('$' + IntToHex(LAddress, SizeOf(Pointer) * 2));

  // 実際にデータを書き込むアドレスを取得
  LAdrData := PNativeInt(LAddress)^;
  Memo1.Lines.Add('$' + IntToHex(LAdrData, SizeOf(Pointer) * 2));
end;


上と同じグローバル変数ですが、New 関数を使用して、対象のポインタ型変数用のメモリを確保すると、データ格納用のメモリアドレスが決まります。
確保したメモリは解放処理が必要ですが、メモリを解放しても、ポインタ型の変数は nil にはなりません。明示的に nil にするには、メモリ解放後に nil を代入します。



図34
ポインタ型の変数用のメモリ確保後
  • ポインタとしてのアドレスが設定される

リスト34
var
  LPDouble : PDouble;

procedure TForm1.Button1Click(Sender: TObject);
var
  LAddress : NativeInt;
  LAdrData : NativeInt;
begin
  Memo1.Lines.Clear;

  // ポインタのサイズ分のメモリを確保
  New(LPDouble);
  try
    // 変数のアドレスを取得
    // このアドレスにはデータを書き込むアドレスの値が格納されている
    LAddress := NativeInt(@LPDouble);
    Memo1.Lines.Add('$' + IntToHex(LAddress, SizeOf(Pointer) * 2));

    // 実際にデータを書き込むアドレスを取得
    LAdrData := PNativeInt(LAddress)^;
    Memo1.Lines.Add('$' + IntToHex(LAdrData, SizeOf(Pointer) * 2));
  finally
    Dispose(LPDouble);
  end;
end;




10_ ポインタ型 PString 型の変数のデータの場所


PString 型は String 型のポインタ型でず。ポインタ型なので前項と同じですが、確認してみます。
ポインタ型の変数のアドレスは、実際のデータが書き込まれているアドレスではありjません。このアドレスのメモリには、実際のデータが書き込まれているアドレスの値が書き込まれています。
下図は 3 つの PString 型の変数のアドレスを、アドレスの値が小さい順に表示しています。これらの値は 32 ビットの EXE における値です。32 ビットの EXE のポインタのサイズは 4 バイトです。したがって、変数のアドレスは 4 バイト単位で増加しています。

ポインタ型の変数が使用するバイトサイズは、SizeOf 関数でも取得できます。



図35
ポインタ型の変数のアドレスの値
  • 32 ビット EXE の場合
  • 1635648 + 4 = 1635652
    1635652 + 4 = 1635656
    4 は 32 ビット EXE のポインタサイズ

リスト35
procedure TForm1.Button1Click(Sender: TObject);
var
  A : PString;
  B : PString;
  C : PString;
  LA_Address : NativeInt;
  LB_Address : NativeInt;
  LC_Address : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // これらのアドレスは String 型の値書き込まれるアドレスではない
  // このアドレスには、文字列としてのデータを書き込むアドレスの値が格納されている
  LA_Address := NativeInt(@A);
  LB_Address := NativeInt(@B);
  LC_Address := NativeInt(@C);

  // 取得した値を表示
  Memo1.Lines.Add('C : ' + IntToStr(LC_Address));
  Memo1.Lines.Add('B : ' + IntToStr(LB_Address));
  Memo1.Lines.Add('A : ' + IntToStr(LA_Address));
end;


ポインタ型の変数は、New 関数でポインタサイズ分のメモリを確保してから操作します。確保したメモリは解放処理が必要です。PString 型のメモリを確保して、メモリアドレスから String 型の文字列を取得した結果が下図です。ポインタ型の逆参照の機能を利用しています。



図36
ポインタ型のデータの格納アドレスからデータを取得

リスト36
procedure TForm1.Button1Click(Sender: TObject);
var
  LPString : PString;
  LAddress : NativeInt;
  LAdrData : NativeInt;
  LDstStr  : string;
begin
  Memo1.Lines.Clear;

  // メモリの確保 (ポインタのサイズ分)
  New(LPString);
  try
    // 変数のアドレスを取得
    // このアドレスには、文字列としてのデータを書き込むアドレスの値が格納されている
    LAddress := NativeInt(@LPString);

    LPString^ := 'PString 型の文字列';

    // 取得したアドレスに書き込まれている値を取得
    // その値が実際にデータを書き込むアドレス
    LAdrData := PNativeInt(LAddress)^;
    Memo1.Lines.Add('$' + IntToHex(LAdrData, SizeOf(Pointer) * 2));

    // 書き込まれているデータを string 型のデータとして取得
    // ポインタ型の逆参照の機能を利用
    LDstStr := PString(LAdrData)^;
    Memo1.Lines.Add(LDstStr);

    Memo1.Lines.Add('');

    // 別の文字列で確認
    LPString^ := 'PString 型の文字列 別の文字列';

    LAdrData := PNativeInt(LAddress)^;
    Memo1.Lines.Add('$' + IntToHex(LAdrData, SizeOf(Pointer) * 2));

    LDstStr := PString(LAdrData)^;
    Memo1.Lines.Add(LDstStr);
  finally
    // 確保したメモリを解放
    Dispose(LPString);
  end;
end;


ポインタとしてのメモリを確保しないでポインタが示すアドレスを調べた結果です。
このアドレスには、実際のデータが書き込まれているアドレスの値が書き込まれますが、下図の値は、いわゆる「ゴミ」データです。
この状態で PString 型の変数に対して逆参照の操作を行うと例外が発生します。



図37
ポインタ型のメモリを確保していない時のデータ格納用アドレス
  • 結果の値は「ゴミ」データ
  • ポインタ型の変数がローカル変数の場合

リスト37
procedure TForm1.Button1Click(Sender: TObject);
var
  LPString : PString;
  LAddress : NativeInt;
  LAdrData : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // このアドレスには、文字列としてのデータを書き込むアドレスの値が格納されている
  LAddress := NativeInt(@LPString);
  Memo1.Lines.Add('$' + IntToHex(LAddress, SizeOf(Pointer) * 2));

  // 取得したアドレスに書き込まれている値を取得
  // その値が実際にデータを書き込むアドレス
  LAdrData := PNativeInt(LAddress)^;
  Memo1.Lines.Add('$' + IntToHex(LAdrData, SizeOf(Pointer) * 2));
end;


上の結果は、ポインタ型の変数がローカル変数の場合です。グローバル変数の場合は、下図のように 0 になります。0 と言うことは、データを格納するアドレス (メモリ上の場所) が存在しないことになります。この状態は、ポインタが nil の状態です。ポインタ型のローカル変数は初期化されませんが、グロール変数は nil に初期化されます。
この状態でポインタ型の変数に対して逆参照の操作を行うと例外が発生します。



図38
ポインタ型のメモリを確保していない時のデータ格納用アドレス
  • 0 となる (nil 状態)
  • つまり、データを格納する場所は存在しない
  • ポインタ型の変数がグローバル変数の場合

リスト38
var
  LPString : PString;

procedure TForm1.Button1Click(Sender: TObject);
var
  LAddress : NativeInt;
  LAdrData : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  // このアドレスには、文字列としてのデータを書き込むアドレスの値が格納されている
  LAddress := NativeInt(@LPString);
  Memo1.Lines.Add('$' + IntToHex(LAddress, SizeOf(Pointer) * 2));

  // 取得したアドレスに書き込まれている値を取得
  // その値が実際にデータを書き込むアドレス
  LAdrData := PNativeInt(LAddress)^;
  Memo1.Lines.Add('$' + IntToHex(LAdrData, SizeOf(Pointer) * 2));
end;


上と同じグローバル変数ですが、New 関数を使用して、対象のポインタ型変数用のメモリを確保すると、データ格納用のメモリアドレスが決まります。
確保したメモリは解放処理が必要ですが、メモリを解放しても、ポインタ型の変数は nil にはなりません。明示的に nil にするには、メモリ解放後に nil を代入します。



図39
ポインタ型の変数用のメモリ確保後
  • ポインタとしてのアドレスが設定される

リスト39
var
  LPString : PString;

procedure TForm1.Button1Click(Sender: TObject);
var
  LAddress : NativeInt;
  LAdrData : NativeInt;
begin
  Memo1.Lines.Clear;

  // メモリの確保 (ポインタのサイズ分)
  New(LPString);
  try
    // 変数のアドレスを取得
    // このアドレスには、文字列としてのデータを書き込むアドレスの値が格納されている
    LAddress := NativeInt(@LPString);
    Memo1.Lines.Add('$' + IntToHex(LAddress, SizeOf(Pointer) * 2));

    // 取得したアドレスに書き込まれている値を取得
    // その値が実際にデータを書き込むアドレス
    LAdrData := PNativeInt(LAddress)^;
    Memo1.Lines.Add('$' + IntToHex(LAdrData, SizeOf(Pointer) * 2));
  finally
    Dispose(LPString);
  end;
end;




11_ ポインタ型に変数のアドレスを代入した時のデータの場所


ポインタ型の変数に、非ポインタ型の変数のアドレスを代入すると、データの格納アドレスが同じになります。このアドレスは、非ポインタ型の変数のアドレスとなります。
メモリを確保したポインタ型の変数に非ポインタ型の変数のアドレスを代入すると、確保したメモリの解放ができなくなります。



図40
ポインタ型の変数に、非ポインタ型の変数のアドレスを代入
  • ポインタ型の変数のデータの格納場所は、非ポインタ型の変数のアドレスとなる

リスト40
procedure TForm1.Button1Click(Sender: TObject);
var
  LPDouble  : PDouble;
  LDouble   : Double;
  LAddressA : NativeInt;
  LAddressB : NativeInt;
  LAdrDataA : NativeInt;
begin
  Memo1.Lines.Clear;

  // ポインタ型の変数に非ポインタ型の変数のアドレスを代入
  // メモリを確保していないので逆参照による代入は不可
  LPDouble := @LDouble;

  // 変数のアドレスを取得
  LAddressA := NativeInt(@LPDouble);
  LAddressB := NativeInt(@LDouble);

  // ポインタが示すアドレスを取得
  LAdrDataA := PNativeInt(LAddressA)^;

  // 実際にデータが書き込まれるアドレスを表示
  Memo1.Lines.Add('$' + IntToHex(LAdrDataA, SizeOf(Pointer) * 2));
  Memo1.Lines.Add('$' + IntToHex(LAddressB, SizeOf(Pointer) * 2));
end;


データの格納アドレスが同じになると、非ポインタ型の変数の値を変更すると、ポインタ型のデータの値は、非ポインタ型のデータと同じになります。その逆の場合も同じです。



図41
ポインタ型の変数と非ポインタ型の変数の相互代入
  • 非ポインタ型のデータを変更するとポインタ型の変数のデータも同じになる
  • ポインタ型のデータを変更すると非ポインタ型の変数のデータも同じになる

リスト41
procedure TForm1.Button1Click(Sender: TObject);
var
  LPDouble  : PDouble;
  LDouble   : Double;
begin
  Memo1.Lines.Clear;

  // ポインタ型でない変数に値を代入
  // メモリを確保していないので逆参照による代入は不可
  LDouble := 1.234E+11;

  // ポインタ型の変数に非ポインタ型の変数のアドレスを代入
  LPDouble := @LDouble;
  Memo1.Lines.Add(FormatFloat('0.0000E+00', LPDouble^));
  Memo1.Lines.Add(FormatFloat('0.0000E+00', LDouble));

  Memo1.Lines.Add('');

  // 非ポインタ型の変数の値を変更
  LDouble := 5.432E+22;
  Memo1.Lines.Add(FormatFloat('0.0000E+00', LPDouble^));
  Memo1.Lines.Add(FormatFloat('0.0000E+00', LDouble));

  Memo1.Lines.Add('');

  // ポインタ型の変数の値を逆参照で変更
  LPDouble ^:= 9.123E+3;
  Memo1.Lines.Add(FormatFloat('0.0000E+00', LPDouble^));
  Memo1.Lines.Add(FormatFloat('0.0000E+00', LDouble));
end;


メモリを確保していないポインタ型の変数に、メモリを確保したポインタ型の変数を代入した場合も、同じ様にデータの格納アドレスが同じになります。
メモリを確保したポインタ型の変数に、メモリを確保したポインタ型の変数を代入すると、代入先のポインタ型用に確保したメモリが解放できなくなります。



図42
メモリを確保していないポインタ型の変数に、メモリを確保したポインタ型の変数を代入
  • データの格納アドレスが同じになる

リスト42
procedure TForm1.Button1Click(Sender: TObject);
var
  LPDoubleA : PDouble;
  LPDoubleB : PDouble;
  LAddressA : NativeInt;
  LAddressB : NativeInt;
  LAdrDataA : NativeInt;
  LAdrDataB : NativeInt;
begin
  Memo1.Lines.Clear;

  // 変数のアドレスを取得
  LAddressA := NativeInt(@LPDoubleA);
  LAddressB := NativeInt(@LPDoubleB);

  // LPDoubleA だけメモリを確保
  New(LPDoubleA);
  try
    // メモリを確保していないポインタ型の変数に代入
    // ポインタの代入なのでポインタが同じになる
    LPDoubleB := LPDoubleA;

    // 実際にデータが書き込まれるアドレスを取得して表示
    LAdrDataA := PNativeInt(LAddressA)^;
    LAdrDataB := PNativeInt(LAddressB)^;
    Memo1.Lines.Add('$' + IntToHex(LAdrDataA, SizeOf(Pointer) * 2));
    Memo1.Lines.Add('$' + IntToHex(LAdrDataB, SizeOf(Pointer) * 2));
  finally
    Dispose(LPDoubleA);
  end;
end;