Single型のポインターの使い方

解決


yTake  2015-09-27 10:42:18  No: 47636

yTakeです。
ポインターの使用方法についてご教授頂けるでしょうか?

バイナリーファイルからSingle型の二次元は配列データを読み出す例を考えます。

procedure single_array_read( binary_f : String ; xx, yy : Word );
var
  fs    ; TFileStream;
  mtx   : Array of Array of Single;
  value : Single;
  cnt   : Word;
  i, j  : Word;
begin
  fs := TFileStream.Create( binary_f, fmOpenRead );
  
  SetLength( mtx, xx );
  for i = 0 to xx - 1 do
    SetLength( mtx[ i ], yy );

  for i = 0 to yy - 1 do
  begin
     for j = 0 to xx - 1 do
     begin
         cnt := fs.Read( value, 4 );
         if cnt = 4 then
         begin
             mtx[ i ][ j ] := value;
         end;
     end;
  end;
  fs.Free();
end;

と、しましたが、これでは配列の個数だけファイルアクセスが発生し、効率が悪い様に思いました。そこで、一行毎に(或いは、全部一括して)ファイルから読み出して、ポインターで配列要素にアクセスする事を考えてみました。

procedure single_array_read( binary_f : String ; xx, yy : Word );
var
  fs    ; TFileStream;
  mtx   : Array of Array of Single;
  pValue: PSingle;
  cnt   : Word;
  size  : Word;
  i, j  : Word;
begin
  fs := TFileStream.Create( binary_f, fmOpenRead );
  
  SetLength( mtx, xx );
  for i = 0 to xx - 1 do
    SetLength( mtx[ i ], yy );

  size := 4 * xx ;
  GetMem( pValue, size );
  for i = 0 to yy - 1 do
  begin
     cnt := fs.Read( pValue^, size );
     for j = 0 to xx - 1 do
     begin
         mtx[ i ][ j ] := ( pValue + i )^;      <-   ①
     end;
  end;
  FreeMem( pValue );
  fs.Free();
end;

これではコンパイルを通りません。①で"E2015 この型には指定した演算子は使えません"とのエラーになります。
Single型のポインターは初めて試しました。Byte型のポインターは使った事があったので、使い方の問題と思います。

例えば①は、"mtx[ i ][ j ] := pValue^;"とすると、コンパイルは通りますが、当然ながら、参照先は先頭データに固定されたままで、読み出しになりません。

Byte型ポインターで試してみました。

procedure single_array_read( binary_f : String ; xx, yy : Word );
var
  fs    ; TFileStream;
  mtx   : Array of Array of Single;
  pValue: PByte;
  cnt   : Word;
  size  : Word;
  i, j  : Word;
  b1, b2, b3, b4 : Byte;
begin
  fs := TFileStream.Create( binary_f, fmOpenRead );
  
  SetLength( mtx, xx );
  for i = 0 to xx - 1 do
    SetLength( mtx[ i ], yy );

  size := 4 * xx ;
  GetMem( pValue, size );
  for i = 0 to yy - 1 do
  begin
     cnt := fs.Read( pValue^, size );
     for j = 0 to xx - 1 do
     begin
         b1 := ( pValue + 4 * i     )^;
         b2 := ( pValue + 4 * i + 1 )^;
         b3 := ( pValue + 4 * i + 2 )^;
         b4 := ( pValue + 4 * i + 3 )^;
         
     end;
  end;
  FreeMem( pValue );
  fs.Free();
end;

これで、Singke型の4ByteデータにByte毎にアクセスできていますが、Single型に戻す方法を見出せていません。
もし、4ByteデータがLong Word型であれば戻し方はわかるのですが。

やはり、ブロック状に読み出して、Singke型へのポインターでアクセス出来ればと思うのですが、思う様にコーディングできません。

Single型ポインターではなく、配列の要素数分だけSingke型変数で読み出すのが無難でしょうか?

よろしくお願いします。


Harry  2015-09-27 22:01:29  No: 47637

>バイナリーファイルからSingle型の二次元は配列データを読み出す例を考えます。
細かく言えば、「…を動的配列に読み出す例」ではないかと思います。
静的配列なら簡単なやり方がありますし。

本題の前に、ちょっとツッコミを。
>  SetLength( mtx, xx );
>  for i = 0 to xx - 1 do
>    SetLength( mtx[ i ], yy );
これ、xxとyyが逆ではないでしょうか。そうでないと、これ以下のfor二重ループと整合しません。

>ポインターの使用方法についてご教授頂けるでしょうか?
かなりポインタに悩み、深みにはまっておられる様子ですね。

実はDelphiでポインタ(またはポインタ的用法?)が必須な局面はごくわずかなのです。
具体的には以下の3つくらいで、他は要りません。ご参考までに。

・ 今回のTFileStream.Readのような「型なしパラメータ」を持つルーチンに文字列型や動的配列型を渡すとき
・ Win32APIなどOSの機能を直接使うとき、またはVCLの中でOS寄りの機能を使うとき
・ Delphiの厳格な型チェックから逃れたり、普通では出来ない気の利いた記述をしたいとき


Harry  2015-09-27 22:06:58  No: 47638

で…、yTake さんの目的が「ファイルからデータを二次元動的配列に読み出したい」のか、
「ポインタの使用方法を知りたい」のか、どちらか分からなかったので、全部お伝えしておきます。

// TFileStreamで1行毎に読み出す
procedure single_array_read_1(binary_f: String; xx, yy: Integer);
var
  mtx: array of array of Single;
  FS: TFileStream;
  I: Integer;
  Size: Integer;
begin
  SetLength(mtx, yy, xx);
  FS:=TFileStream.Create(binary_f, fmOpenRead or fmShareDenyWrite);
  try
    for I:=Low(mtx) to High(mtx) do begin
      Size:=SizeOf(mtx[I][0])*Length(mtx[I]);
      FS.ReadBuffer(Pointer(mtx[I])^, Size);
    end;
  finally
    Fs.Free;
  end;
  // 読み出したデータを処理
end;

// TMemoryStreamでファイルを全部Loadしておき、1行毎に読み出す
procedure single_array_read_2(binary_f: String; xx, yy: Integer);
var
  mtx: array of array of Single;
  MS: TMemoryStream;
  I: Integer;
  Size: Integer;
begin
  SetLength(mtx, yy, xx);
  MS:=TMemoryStream.Create;
  MS.LoadFromFile(binary_f);
  try
    for I:=Low(mtx) to High(mtx) do begin
      Size:=SizeOf(mtx[I][0])*Length(mtx[I]);
      MS.ReadBuffer(Pointer(mtx[I])^, Size);
    end;
  finally
    MS.Free;
  end;
  // 読み出したデータを処理
end;

// あえてポインタを使ってみる(ただし無駄な処理が増えます)
procedure single_array_read_3(binary_f: String; xx, yy: Integer);
var
  mtx: array of array of Single;
  Size: Integer;
  P: Pointer;
  pValue: PSingle;
  FS: TFileStream;
  I, J: Integer;
begin
  SetLength(mtx, yy, xx);
  Size:=SizeOf(pValue)*xx*yy;
  GetMem(P, Size);
  pValue:=P;
  try
    FS:=TFileStream.Create(binary_f, fmOpenRead or fmShareDenyWrite);
    try
      FS.ReadBuffer(pValue^, Size);
    finally
      Fs.Free;
    end;
    for I:=Low(mtx) to High(mtx) do begin
      for J:=Low(mtx[I]) to High(mtx[I]) do begin
        mtx[I][J]:=pValue^;
        Inc(pValue);
      end;
    end;
  finally
    FreeMem(P);
  end;
  // 読み出したデータを処理
end;

なお私は普段GetMemとかまったく使いませんので、ポインタを使った例が適切か自信がありません。
もしツッコミ等ありましたら、どなたかよろしく。


通りすがり  2015-09-27 22:09:08  No: 47639

バージョンも書かずに質問とな?

Pointer Math (Delphi) - RAD Studio (日本語)
http://docwiki.embarcadero.com/RADStudio/2010/ja/Pointer_Math_%EF%BC%88Delphi%EF%BC%89


Harry  2015-09-27 22:14:51  No: 47640

TMemoryStreamの例、tryの位置を間違えてました…大事なところなのに。

>  MS:=TMemoryStream.Create;
>  MS.LoadFromFile(binary_f);
>  try

正しくは↓

  MS:=TMemoryStream.Create;
  try
    MS.LoadFromFile(binary_f);

でした。失礼しました。


yTake  2015-09-28 02:59:35  No: 47641

Harryさん、通りすがりさん、ありがとうございます。

先ず始めに、質問の際、環境情報を明示していなくてすみません。
OS:32bit版Win7
DELPHI:XE3
です。

また、質問の意図としましては、ファイルからSingle型の二次元配列を読み出す事が目的ですが、高速化の為に、ポインターを使ってはどうか?と考えました。

ご教授によりますと、DELPHIにおきましては、ポインター自体の利用の場が限られる様で、大変参考になります。

実際、Readで動的配列へ直接読み込めばポインターで要素をコピーする様な操作は不要ですね。
また、ポインターをあえて使う場合でも、inc( pValue )などの様な使い方が出来る事が分かり勉強になりました。

結論的には、配列に読み込む場合、配列要素個別操作する必要はないと言う事ですね。
配列メモリに直接、行データ毎読み込めば高速かつ無駄がないと理解しました。

大変ありがとうございました。


Harry  2015-09-28 06:57:02  No: 47642

すみません、訂正です。ポインタを使った例に致命的ミスがありました。

誤  Size:=SizeOf(pValue)*xx*yy;
正  Size:=SizeOf(pValue^)*xx*yy;

でした。失礼しました。

SizeOf(pValue) だと SizeOf(Pointer) と同義になってしまいます。
今回の場合、Single も Pointer も4Byteなので、問題が表面化していないという状況です。
なお、SizeOf(pValue^) は SizeOf(Single) と同義になります。

ところで…今回は記述ミスでハマってしまいましたが、、、それであっても、
  Size:=4*xx*yy;
みたいな、型のサイズをベタ書きすることは忌避されております。その理由は、
・ リテラルの 4 がマジックナンバー(出どころ、根拠が不明)となり、ソースの可読性が低下する。
・ 使用する型を変更(たとえば Double)したときに、ソースの修正箇所の確認に手間取る。
・ SizeOfを使っておけば、もし将来、型のサイズが変わったり、環境によって型のサイズが
異なったりする場合でも、ソースの変更が不要になります。


yTake  2015-09-28 08:09:43  No: 47643

Harryさん

訂正のお知らせをありがとうございます。

ご指摘の点、ちょうど、こちらでも確認していました。
ポインターで指し示すデータ型のサイズを知る為なので、 ^ を付加する必要があると解釈しました。訂正のリプライもあったので確証がもてました。

また、SIzeOf関数の使用の優位性を改めて認識しました。
今後は、機をつけたいと思います。

重ねて、ありがとうございます。


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

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






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