Pointer型の配列の使い方

解決


delrin  2008-10-22 23:27:37  No: 32297

type
  TByteArray = array [0..MaxInt - 1] of Byte; ★1
  // TByteArray = array [0..1] of Byte; ★2
  PByteArray = ^TByteArray;

var
  MS: TMemoryStream;
  Data: PByteArray;
---------------------------------
 
MS := TMemoryStream.Create;
try
  MS.LoadFromFile('test.dat'); // not演算で暗号化されたファイル(5,059 バイト)
  Data := MS.Memory;

  // not演算の暗号を解く
  for i := 0 to MS.Size - 1 do
    Data^[i] := not Data^[i]; ★3
    // Data[i] := not Data[i]; ★4

  MS.SaveToFile('test_fukugen.dat');
---------------------------------

上記のようなプログラムを実行させると暗号化が解けるのですが、
仕組みがよく分からないのでお教え願えないでしょうか?

知りたいのは下記の2点です。

1.★1を削除して★2にした場合でも復号化ができる点

  暗号ファイルが 5,059 バイトあるので、配列の要素も5059以上にしないと
  いけないという解釈だったのですが、★2に変えてもfor文で5059文処理して
  復号できてしまった。どうしてでしょうか?

2.★3を★4に変えても復号できる、また^を右辺左辺どちらか一方に
付けた場合も復元できる

  ^が付く場合と付かない場合は何が違うのでしょうか?

よろしくお願いします。


うんと  2008-10-23 06:09:09  No: 32298

1.は、配列のインデックスが範囲を超えたとき例外が生ずるのは、メモリの
割り当てが出来てないからですね。今回は、その心配もなく、実際、

>TByteArray = array [0..1] of Byte;

ではなく

TByteArray = array of Byte;

と宣言するほうが合理的です。

2.は Delphi のコンパイラの特徴ですね。
ポインタの参照は、逆参照 ^ があってもなくても、ポインタの指す内容への
参照になります。個人的には必ず ^ をつけます。


delrin  2008-10-23 23:00:14  No: 32299

うんとさん、返信ありがとうございます。

2.についてはそういう仕様だったのですね。

1.についてですが、例外が出ているわけではなくて、

type
  TByteArray = array [0..1] of Byte;
  PByteArray = ^TByteArray;
var
  Data: PByteArray;

としているのに

for i := 0 to MS.Size - 1 do
    Data^[i] := not Data^[i];

↑で「i」が「MS.Size - 1」までfor文が実行されます。
「MS.Size - 1」の値を小さくすると(MS.Size - 2)、
復号はされません。
また「MS.Size - 1」の値を大きくしても(MS.Size + 5)
for文が実行され復号されます。★1

Dataの要素数はどうやって設定されるのでしょうか?

Data := MS.Memory;  が関係ありそうですが、ヘルプでは

「Memory プロパティは,メモリストリームに割り当てた
メモリプールに直接アクセスできるようにします。」

と書いてあります。

これが自動的にDataの要素数を設定しているのでしょうか?
(そうだとしても、★1の部分が謎ですが)


ナゾナゾ  2008-10-23 23:33:05  No: 32300

SysUtils.pasで↓このように定義されてるので、普通は自前の定義は不要。
  PByteArray = ^TByteArray;
  TByteArray = array[0..32767] of Byte;

それはさておき、↓このように使用メモリより少ない定義などするもんじゃないね。
TByteArray = array [0..1] of Byte;
デフォルトの設定では範囲チェックしない{$R-}のでエラーにならないけど、
{$R+}なら例外が発生する。

var
  Data: PByteArray;
  i: integer;
begin
  with TMemoryStream.Create do try
   LoadFromFile('ふぁいる');
   Data := Memory;
{$R+}
   for i:=0 to Size-1 do begin
    Data^[i] := not Data^[i];
   end;
{$R-}
  finally
   Free;
  end;

開発段階ではキチンと範囲チェックを有効にしておいた方がいいよ。
そうしないと思わぬバグが潜むことあり。
ただし、今回のような場合は Dataの要素数を指定する必要はないし、
実行時に変動する要素数は決められないということ。

TByteArray = array of Byte;


おっと  2008-10-24 00:06:03  No: 32301

書き損じかな。
> TByteArray = array of Byte;
動的配列ではまずいね。
  TByteArray = array[0..0] of Byte;
  PByteArray = ^TByteArray;
当然、この場合は{$R+}では例外が発生するので{$R-}にしておかないと。


うんと  2008-10-24 05:31:51  No: 32302

いまの場合、ポインタの配列と、その配列の先頭をあらわすポインタ変数を
混乱しているようです。ポインタ以外の配列でも、たとえば

type
PVariableArray = ^TVariable;
TVariable = array of Variable;

とした場合、

var
Data :PVariableArray;

の場合、Data は Variable の配列の先頭を表すポインタです。

>1.についてですが、例外が出ているわけではなくて、

はい、最初の回答にも例外が出てるとは書いてません。

>「MS.Size - 1」の値を小さくすると(MS.Size - 2)、復号はされません。

それは何かの勘違いでは? 最後の1バイトだけ復号されないのは当然ですが。

>また「MS.Size - 1」の値を大きくしても(MS.Size + 5)  for文が実行され復号されます。★1

それはそうでしょう。TMemoryStream の内部で確保しているメモリの量は
一般に Size より大きいのでたまたま アクセスバイオレーション が
出なかっただけで、Size を越えてアクセスしてはいけません。

わたしだったら、

var
  MS: TMemoryStream;
  Data: PByte; <===== 注目!
---------------------------------
 
MS := TMemoryStream.Create;
try
  MS.LoadFromFile('test.dat'); 
  Data := MS.Memory;  // この代入により Data は MS が持ってるメモリの先頭を指す

  for i := 0 to MS.Size - 1 do
  begin
    Data^ := not Data^; 
    Inc(Data);
  end;

  MS.SaveToFile('test_fukugen.dat');
---------------------------------

ように書きます。

(すみませんが今はDelphiの環境に触れないので確認はしてません)


delrin  2008-10-24 23:03:29  No: 32303

ナゾナゾさん、返信ありがとうございます。

{$R+}で確かに例外が発生しました。
今回のようなケースでは要素数を指定しなくても
良いのですね。
不安だったのですが、これですっきりしました。

うんとさん、返信ありがとうございます。

>「MS.Size - 1」の値を小さくすると(MS.Size - 2)、復号はされません。
このファイルは全バイトnot演算することで復元されるファイルです。
説明不足で申し訳ありませんでした。

サンプルの提示ありがとうございます。
試したところ、正常に動作しました。
.Memoryの意味もこれで理解できました。
とても勉強になります。

解りやすく説明していただきとても助かりました。
この度はありがとうございました。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加