SetLengthで不可解なエラー

解決


yTake  2018-07-22 19:09:22  No: 49303

動的配列に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プロジェクトです。


Mr.XRAY  2018-07-23 00:04:30  No: 49304

>今回は配列の作成時なので範囲外

配列の「サイズ設定時」にも範囲外のエラーは発生することはあります.

コードを眺めているだけで動作を確認できるだけの能力はありませんので,
テストプロジェクトを「新規」に作成してテストしてみました.
興味ある方はテストしてみてください.

56 KB (EXE なし)
http://mrxray.on.coocan.jp/Delphi/zip/QandA_SetLength_20180722.zip


yTake  2018-07-23 02:11:57  No: 49305

MR.XRAYさん、ありがとうございます。

テストプロジェクトを提示頂きありがとうございます。
お手数をお掛けしました。

私のコードに転記ミスがあった様です。すみません。
ただ、こちらでは構文エラーもありませんでしたので、純粋に転記ミスです。

一方で、MR.XRAYさんのテストプロジェクトは無事エラーなく実行できました。
一部クラス内で変数の定義場所が違いましたが、これが原因ではない様です。

こちらで、MR.XRAYさんの様にPublicとして定義しましたが、改善されません。

いずれにしましても、SetLengthの使い方や配列の定義場所には問題が無い事が分かりました。

後は、周辺での操作が影響していると思いますが、MR.XRAYさんのテストプロジェクトにこちらで行っている操作を少しずつ肉付けしたのですが、Access Violationは発生しません。

こちらのプロジェクトではなぜAccess Violation となるのか、分かりません。

ファイルから読み込んだデータを二次元配列へ格納するだけなのですが、こちらではTFileStreamを用いて一括で読み込みポインターを介して要素を配列に格納しています。
テストプロジェクトにその様に肉付けした場合はエラーなく二次元配列の格納されますが、こちらのプロジェクトでは必要な二次元配列をSetLengthする時点でAccess Violationとなります。
試しに、こちらのプロジェクトでTFileStreamでデータを読み込む部分を省くとSetLengthでのAccess Violationは出ません。(当然、データ格納は出来ません)

やはり、この辺の操作が良からぬ影響を与えている様です。

もう少し、試してみます。


yTake  2018-07-23 02:31:00  No: 49306

解決しました。

MR.XRAYさん、ありがとうございました。

結局、SetLengthは問題ありませんでした。

二次元配列へ格納するデータを読み込む際、ファイルから一括読み出しを行う際のメモリー領域の確保が間違っていました。Single型へのポインターで参照するつもりだったので、配列サイズx4とすべきところをx2(Word型)としてしまっていました。実際の読み出しは、配列サイズx4で読み出していたので、確保したメモリー領域を超えて読み込んでいました。

極基本的な誤りでしたが、MR.XRAYさんのテストプロジェクトがあったおかげでその誤りに気が付きました。

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


Mr.XRAY  2018-07-23 08:51:39  No: 49307

>配列サイズ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;


yTake  2018-07-25 00:24:10  No: 49308

MR.XRAYさん

ありがとうございます。

そうですね。

変数型のサイズの機種依存を避ける為にも、Sizeof関数の使用がよく推奨されている事ですよね。

今回は機種依存の恐れもなくうっかりしました。
まさかのサイズ違いで思いのほか路頭に迷っていました。
これに肝に銘じてどんな場合でもSizeofの使用を心がけます。


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

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






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