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型変数で読み出すのが無難でしょうか?
よろしくお願いします。
>バイナリーファイルから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の厳格な型チェックから逃れたり、普通では出来ない気の利いた記述をしたいとき
で…、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とかまったく使いませんので、ポインタを使った例が適切か自信がありません。
もしツッコミ等ありましたら、どなたかよろしく。
バージョンも書かずに質問とな?
Pointer Math (Delphi) - RAD Studio (日本語)
http://docwiki.embarcadero.com/RADStudio/2010/ja/Pointer_Math_%EF%BC%88Delphi%EF%BC%89
TMemoryStreamの例、tryの位置を間違えてました…大事なところなのに。
> MS:=TMemoryStream.Create;
> MS.LoadFromFile(binary_f);
> try
正しくは↓
MS:=TMemoryStream.Create;
try
MS.LoadFromFile(binary_f);
でした。失礼しました。
Harryさん、通りすがりさん、ありがとうございます。
先ず始めに、質問の際、環境情報を明示していなくてすみません。
OS:32bit版Win7
DELPHI:XE3
です。
また、質問の意図としましては、ファイルからSingle型の二次元配列を読み出す事が目的ですが、高速化の為に、ポインターを使ってはどうか?と考えました。
ご教授によりますと、DELPHIにおきましては、ポインター自体の利用の場が限られる様で、大変参考になります。
実際、Readで動的配列へ直接読み込めばポインターで要素をコピーする様な操作は不要ですね。
また、ポインターをあえて使う場合でも、inc( pValue )などの様な使い方が出来る事が分かり勉強になりました。
結論的には、配列に読み込む場合、配列要素個別操作する必要はないと言う事ですね。
配列メモリに直接、行データ毎読み込めば高速かつ無駄がないと理解しました。
大変ありがとうございました。
すみません、訂正です。ポインタを使った例に致命的ミスがありました。
誤 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を使っておけば、もし将来、型のサイズが変わったり、環境によって型のサイズが
異なったりする場合でも、ソースの変更が不要になります。
Harryさん
訂正のお知らせをありがとうございます。
ご指摘の点、ちょうど、こちらでも確認していました。
ポインターで指し示すデータ型のサイズを知る為なので、 ^ を付加する必要があると解釈しました。訂正のリプライもあったので確証がもてました。
また、SIzeOf関数の使用の優位性を改めて認識しました。
今後は、機をつけたいと思います。
重ねて、ありがとうございます。
ツイート | ![]() |