動的配列にSetLengthをする場合、サイズにより成功する場合と失敗する場合があります。
Unit3, Unit4からなるプロジェクトです。
Unit3ではFormを作成しています。Unit4ではFormは用いていません。
配列は、自作Class内でRecord型の二次元配列を定義しています。
実行時、配列サイズを取得して定義するのですが、あるサイズ以上でSetLengthすると例外クラス$C0000005(Access Violation)のエラーになります。普通、配列でAccess Violationの場合、配列の範囲外を参照してしまった場合などが多いですが、今回は配列の作成時なので範囲外と言う以前の様に思います。
または、指定されたサイズを確保するはずのアドレスが不正な場所だった?
但し、同じプログラムの同じ動的配列で、正常にSetLength出来る場合があります。
動的配列は、Unit4で定義していて、Unit3内のプログラム中でサイズを設定しています。
Unit3で実装直後では、サイズに制限なくSetLength出来るのですが、Unit3で関数として呼び出した後に、Unit4内の関数実装部でSetLengthを実行すると二次元目のサイズを8より大きく設定すると、Access Violationになります。
Unit3
vvvvvvvvvvvvvvvvvvv
implement
uses
Unit4;
procedure TForm3.Button1Click(sender: TObject);
begin
mapmtx1 := TMAPMTX.Create();
SetLength( mapmtx1.MEM.MTX, 100, 100 ); // ここではサイズよらず成功
mapmtx1.create_mtx( 100, 100 ); // 代わりに関数内でSetLengthするとエラー
end;
^^^^^^^^^^^^^^^^^^^
Unit4
vvvvvvvvvvvvvvvvvvv
Interface
type
RMAP = Record
den : Word;
od : Single;
disp : Byte;
End;
type
RMTX = Record
sizX: Word;
sizY: Word;
MTX : Array of Array of RMAP;
End;
type
TMAPMTX : Class
str : String;
MEM : RMTX;
private
public
procedure create_mtx();
End;
var
mapmtx1 : TMAPMTX;
implement
procedure TMAPMTX.create_mtx( x, y : Word );
begin
SetLength( MEM.MTX, x, y ); // ここでサイズによりエラー
end;
^^^^^^^^^^^^^^^^^^^
抜粋の為、分かり難いかも知れません。
SetLengthするUnitによって配列サイズに制限がかかると思思えません。
配列の定義やSetLengthの使い方の様に思えますが、原因が分かりません。
以前はこの配列の定義やSetLengthの使い方で問題無かったと思うのですが、比べても違い或いは間違いを見つけられません。
よろしくお願い致します。
環境は、XE6でVCLプロジェクトです。
>今回は配列の作成時なので範囲外
配列の「サイズ設定時」にも範囲外のエラーは発生することはあります.
コードを眺めているだけで動作を確認できるだけの能力はありませんので,
テストプロジェクトを「新規」に作成してテストしてみました.
興味ある方はテストしてみてください.
56 KB (EXE なし)
http://mrxray.on.coocan.jp/Delphi/zip/QandA_SetLength_20180722.zip
MR.XRAYさん、ありがとうございます。
テストプロジェクトを提示頂きありがとうございます。
お手数をお掛けしました。
私のコードに転記ミスがあった様です。すみません。
ただ、こちらでは構文エラーもありませんでしたので、純粋に転記ミスです。
一方で、MR.XRAYさんのテストプロジェクトは無事エラーなく実行できました。
一部クラス内で変数の定義場所が違いましたが、これが原因ではない様です。
こちらで、MR.XRAYさんの様にPublicとして定義しましたが、改善されません。
いずれにしましても、SetLengthの使い方や配列の定義場所には問題が無い事が分かりました。
後は、周辺での操作が影響していると思いますが、MR.XRAYさんのテストプロジェクトにこちらで行っている操作を少しずつ肉付けしたのですが、Access Violationは発生しません。
こちらのプロジェクトではなぜAccess Violation となるのか、分かりません。
ファイルから読み込んだデータを二次元配列へ格納するだけなのですが、こちらではTFileStreamを用いて一括で読み込みポインターを介して要素を配列に格納しています。
テストプロジェクトにその様に肉付けした場合はエラーなく二次元配列の格納されますが、こちらのプロジェクトでは必要な二次元配列をSetLengthする時点でAccess Violationとなります。
試しに、こちらのプロジェクトでTFileStreamでデータを読み込む部分を省くとSetLengthでのAccess Violationは出ません。(当然、データ格納は出来ません)
やはり、この辺の操作が良からぬ影響を与えている様です。
もう少し、試してみます。
解決しました。
MR.XRAYさん、ありがとうございました。
結局、SetLengthは問題ありませんでした。
二次元配列へ格納するデータを読み込む際、ファイルから一括読み出しを行う際のメモリー領域の確保が間違っていました。Single型へのポインターで参照するつもりだったので、配列サイズx4とすべきところをx2(Word型)としてしまっていました。実際の読み出しは、配列サイズx4で読み出していたので、確保したメモリー領域を超えて読み込んでいました。
極基本的な誤りでしたが、MR.XRAYさんのテストプロジェクトがあったおかげでその誤りに気が付きました。
大変ありがとうございました。
>配列サイズx4とすべきところをx2(Word型)としてしまっていました。
余計なお世話かも知れませんが,型のサイズは SizeOf 関数で取得した値を使用した方がよろしいかと.
procedure TForm1.Button1Click(Sender: TObject);
var
LSize : Integer;
begin
Memo1.Lines.Clear;
LSize := SizeOf(Single);
Memo1.Lines.Add(IntToStr(LSize));
LSize := SizeOf(WORD);
Memo1.Lines.Add(IntToStr(LSize));
end;
MR.XRAYさん
ありがとうございます。
そうですね。
変数型のサイズの機種依存を避ける為にも、Sizeof関数の使用がよく推奨されている事ですよね。
今回は機種依存の恐れもなくうっかりしました。
まさかのサイズ違いで思いのほか路頭に迷っていました。
これに肝に銘じてどんな場合でもSizeofの使用を心がけます。