喫茶XRAYトップページ

[スレッド全体
]

[9927] 今更きけない 動的配列…(いや、まぁ聞くんだけどさ)返信 削除
2016/5/15 (日) 19:47:35 taki
マスター 教えてください。
動的配列を使う度に、以下の記事が気になって心配になります。

------------------------------------------
Delphi/動的配列の罠
http://tobysoft.net/wiki/index.php?Delphi%2F%C6%B0%C5%AA%C7%DB%CE%F3%A4%CE%E6%AB

動的配列を「string のようなものだ」と思っていると痛い目に合います。
動的配列は参照カウンタつきポインタ

「代入したときは参照カウンタをインクリメント」してくれ、
「スコープが外れたときは、参照カウンタをデクリメントし、
  0になったら自動的に開放」はしますが、
「書き換えた時にコピー(copy on write)」はしてくれないのです。
------------------------------------------

説明はヘルプの「動的配列」のところに書いてあるそうなのですが、
Delphi XP4 Starter のHELPでキーワードに動的配列を入力しても説明が
見つかりません。

記事の説明のコードを見ると、単純に := で代入をするとポインタが
コピーされるので、「代入元と代入先が同じメモリーを参照します」と
読めるのですが、他に何か罠があるのでしょうか?

言葉で分からないのは「参照カウンタつきXX」の部分です。


[9932] Re:今更きけない 動的配列…(いや、まぁ聞くんだけどさ)返信 削除
2016/5/16 (月) 12:07:50 Mr.XRAY
▼ takiさん

> マスター 教えてください。

分かりません !! (キッパリ)

> Delphi/動的配列の罠
> http://tobysoft.net/wiki/index.php?Delphi%2F%C6%B0%C5%AA%C7%DB%CE%F3%A4%CE%E6%AB


あっ,これ,面白い !

>だから、「動的配列はポインタだ」という事だけ気をつけておけば
>いいのでしょうか?


だからか,どうかは分かりませんが (^^;
私的な立場では... ホホホッ.


[9942] 動的配列とポインタと型と...返信 削除
2016/5/16 (月) 18:19:38 Mr.XRAY
> >だから、「動的配列はポインタだ」という事だけ気をつけておけば
> >いいのでしょうか?
>
> だからか,どうかは分かりませんが (^^;
> 私的な立場では... ホホホッ.


誤解されると困るので,一応.
以下も動的配列といいますが,これは,代入できません.
([DCC エラー] Unit1.pas(64): E2008 互換性の無い型です) となります.

procedure TForm1.Button1Click(Sender: TObject);
var
  Array1 : array of Integer;
  Array2 : array of Integer;
begin
  // array1の確保と変更
  SetLength(Array1, 10);
  SetLength(Array2, 10);

  Array2 := Array1;  //これはエラー


この代入が可能なのは,type 節で定義した型やクラスのインスタンスです.
(コンポーネントもクラスです)
または,String 型や Integer 型のような単純な型 (?) の同じ型の変数同志です.
例えば,TIntegerDynArray は次のように定義されています.

type
  TIntegerDynArray = array of Integer;


ちょっ前のスレッドにありましたが,HQUERY もそうです.
HQUERY は Type で宣言されています.そのため,型が違うというエラーに
なったわけです.

type で前言した「型」は,実はポインタということですね.


[9946] Re:動的配列とポインタと型と...返信 削除
2016/5/16 (月) 20:07:16 taki
▼ Mr.XRAYさん

手続き(関数)の引数として動的配列を使うときに注意が必要という事みたいです。

type
  TTestAry = array of integer;

procedure ABC(buf:TTestAry);
begin
  Form1.Label1.Caption := inttostr(buf[0]);   // 999
  // 何の気なしに手続き内で値を変更する
  buf[0] := 888;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Abuf:TTestAry;
begin
  SetLength(Abuf,1);
  Abuf[0] := 999;
  // 手続きを呼び出す
  ABC(Abuf);
  // Abufの値が変化してしまう
  Label2.Caption := inttostr(Abuf[0]);   // 888
end;

これって
procedure ABC(var buf:TTestAry); と同じですよね。

『var』と書いていないのに var 扱いになる!!
踏んじゃいそうですね。



[9962] Re2:動的配列とポインタと型と...返信 削除
2016/5/19 (木) 19:44:56 taki
> これって
> procedure ABC(var buf:TTestAry); と同じですよね。
>
> 『var』と書いていないのに var 扱いになる!!
>


関数(手続き)内で配列のサイズを指定してデータをセットする場合、
「Varなし」と「Varあり」とでは働きがちがうようです。

type
  TTestAry = array of integer;

// Varなし手続きの場合
procedure ABC(buf:TTestAry);
begin
  SetLength(buf,1);       // 手続き内で配列のサイズを決定
  buf[0] := 999;
  Form1.Label1.Caption := inttostr(buf[0]);   // 999
  // 手続き内で値を変更する
  buf[0] := 888;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Abuf:TTestAry;
begin
  // 手続きを呼び出す
  ABC(Abuf);
  Label2.Caption := inttostr(Abuf[0]);   // Abuf は nil で、エラーになる
end;

///-------------------------------------------

// Varあり手続きの場合
procedure VarABC(var buf:TTestAry);
begin
  SetLength(buf,1);       // 手続き内で配列のサイズを決定
  buf[0] := 999;
  Form1.Label1.Caption := inttostr(buf[0]);   // 999
  // 手続き内で値を変更する
  buf[0] := 888;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  Abuf:TTestAry;
begin
  // 手続きを呼び出す
  VarABC(Abuf);
  Label2.Caption := inttostr(Abuf[0]);   // 888
end;

えっ、もうややこしいこと言うなって?

すみませんね。
私はこんな使い方しているもんで、それで気が付いたのでご報告を。


[9963] Re3:動的配列とポインタと型と...返信 削除
2016/5/19 (木) 21:48:06 Mr.XRAY
▼ takiさん

> > 『var』と書いていないのに var 扱いになる!!

正確には違いますが...
動作そのものは,taki さんが提示された通りですね.

手続きの中で動的配列の SetLength を初めて実行するということは,
クラスで言えば,手続きの中でそのクラスのインスタンスを生成
することと同じです.
こういう使い方はしたことがないです.私は.

> 私はこんな使い方しているもんで、それで気が付いたのでご報告を。

報告ということで... (^^;


[9965] インスタンスの生成をどこでやるか返信 削除
2016/5/19 (木) 22:44:01 Mr.XRAY
> こういう使い方はしたことがないです.私は.

基本的に以下のような使い方をしています.
インスタンスの生成は,呼び出し側で行う方法です.
この方法が,割と一般的だとは思います.
クラス等は,呼出し側で生成と破棄を実行した方が,管理がしやすくなります.


type
  TTestAry = array of integer;


procedure ABC(buf:TTestAry);
begin
  //呼出し側でbufのインスタンスを生成していなければ例外発生
  buf[0] := 999;
  Form1.Label1.Caption := inttostr(buf[0]);   // 999
  // 手続き内で値を変更する
  buf[0] := 888;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Abuf:TTestAry;
begin
  //インスタンス生成(と同じ)
  SetLength(Abuf,1);

  // 手続きを呼び出す
  ABC(Abuf);
  Label2.Caption := inttostr(Abuf[0]);

  //動的配列の解放はしなくてもOK
  //明示的に実行するのであればnilを代入する
  Abuf := nil;
end;



[9967] Re:インスタンスの生成をどこでやるか返信 削除
2016/5/20 (金) 19:00:25 taki
▼ Mr.XRAYさん
> > こういう使い方はしたことがないです.私は.
>
> 基本的に以下のような使い方をしています.
> インスタンスの生成は,呼び出し側で行う方法です.
> この方法が,割と一般的だとは思います.
> クラス等は,呼出し側で生成と破棄を実行した方が,管理がしやすくなります.
>


やはり、そうですよね。
ありがとうございました。


[9971] Re2:インスタンスの生成をどこでやるか返信 削除
2016/5/21 (土) 22:58:38 taki
う〜ん、手続き内で動的配列の数を変更すると Ver 指定する必要があるようです。
Ver が無いと100〜109しか表示されません。

type
  TTestAry = array of integer;

procedure ABC(var buf:TTestAry);
var
  i,len,count:integer;
begin
  len := Length(buf);  // 今のサイズを得る
  count := 0;

  // 100〜200のデータをbufにセットする処理
  for i := 100 to 200 do begin
     buf[count] := i;
     inc(count);

    // 配列の数を増やす ---------
    if count >= len then begin
      len := len + 10;    // とりあえず10増やす
      SetLength(buf,len);
    end;
    // --------------------------
  end;

  // 終了前に配列の数を実際の数に揃える
  SetLength(buf,count);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Abuf:TTestAry;
  i,len:integer;
begin
  // 動的配列設定
  SetLength(Abuf,10);   // とりあえず10確保

  // 手続きを呼び出す
  ABC(Abuf);

  // 結果の表示
  len := Length(Abuf);
  for i := 0 to len - 1 do begin
    ListBox1.Items.Append(inttostr(Abuf[i]));
  end;
end;


ListBox1.Items.Append()のように使えたらと思ったのですが、
どうもちがうようです。

やはり呼び出し側でSetLength(Abuf,10000)とかにし、ABCを関数にして
実際に処理した数を返すようにするのが正解かもしれません。


[9973] Re3:インスタンスの生成をどこでやるか返信 削除
2016/5/22 (日) 01:30:27 Nov
▼ takiさん
> う〜ん、手続き内で動的配列の数を変更すると Ver 指定する必要があるようです。
> Ver が無いと100〜109しか表示されません。


SetLengthすると、bufのアドレスが変わりませんか?

つまり、varはポインタそのものに効果があるだけですよね(たぶん)。

[9974] Re4:インスタンスの生成をどこでやるか返信 削除
2016/5/22 (日) 01:48:11 Nov
> つまり、varはポインタそのものに効果があるだけですよね(たぶん)。

あ、これだけ読むとなんのことかよくわからないですね。
「ポインタにvarをつけた場合は」ってことです。

[9975] Re5:インスタンスの生成をどこでやるか返信 削除
2016/5/22 (日) 12:09:13 Mr.XRAY
▼ Novさん
> > つまり、varはポインタそのものに効果があるだけですよね(たぶん)。
>
> あ、これだけ読むとなんのことかよくわからないですね。
> 「ポインタにvarをつけた場合は」ってことです。


ですね.クラス,動的配列の引数は,元々クラス参照と同じとなります.
var を付けると,その参照となります.
動的配列は,Create しなくても使用できてしまうので,Class 型で
確認してみます.
(注: このコードはメモリリークが発生します)

type
  TTestClass = class(TObject);


procedure ABC(var AClass: TTestClass);
var
  P : PInteger;
begin
  AClass := TTestClass.Create;
  P := @AClass;
  Form1.Label2.Caption := IntToStr(P^);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  AClass : TTestClass;
  P      : PInteger;
begin
  AClass := nil;
  AClass := TTestClass.Create;
  try
    P := @AClass;
    Label1.Caption := IntToStr(P^);

    ABC(AClass);
    if AClass <> nil then begin
      P := @AClass;
      Label3.Caption := IntToStr(P^);
    end;
  finally
    FreeAndNil(AClass);
  end;
end;




[9976] Re6:インスタンスの生成をどこでやるか返信 削除
2016/5/22 (日) 12:35:17 Mr.XRAY
> ですね.クラス,動的配列の引数は,元々クラス参照となります.
> var を付けると,その参照となります.


あまり正確とは言えませんが,当サイトの記事には,左側に目次があります.
その目次をクリックすると,目次のタイトルに応じた本文が表示されます.
引数を「値」で渡す場合,この目次のタイトルそのものが渡されます.
その「値」の引数に var を付けると,目次のタイトルを,関数内で
変更できます.
var は元々変数の定義に使われ,それは値を変更できますから.

引数がクラス等のポインタの場合,目次の場所,アドレスが渡されます.
つまり,目次のタイトルそのものは関数内では変化しません.
目次のアドレス(記事のアドレスであるURL)が示す,記事の内容を取り出したり,
変更することができます.

もし,それに var を付けると,... ということになりますね.
ということで,参照渡しの引数に var を付けるのは,
一般的には使用することがないということになります.
私としては,var を使わない方向でコーディングを考えることをお勧めます.
と言うより,そうすべきと考えていますです.ハイ.


[9978] Re7:インスタンスの生成をどこでやるか返信 削除
2016/5/22 (日) 18:15:55 Mr.XRAY
> と言うより,そうすべきと考えていますです.ハイ.

いや,私は var を使いたい,というのであれば,やぶさかではございません.
人様のコーディングに口出しするような,そんな恐ろしいことはできません.ハイ.(^^;


[9980] Re8:インスタンスの生成をどこでやるか返信 削除
2016/5/22 (日) 19:28:54 taki
▼ マスター、Novさん

色々と説明して頂き恐縮です。

何度も読み返したのですが、私の頭では理解できません。
それで、以下のコードを試してみました。

それぞれのポイントでの変数の内容と変数のアドレスを
表示するようにしただけです。


type
  TTestAry = array of integer;

procedure ABC(var buf:TTestAry);
var
  i,len,count:integer;
  s:string;
begin
  s := 'ABC手続 buf ' + IntToHex(Integer(buf),8);
  Form1.listbox1.Items.Append(s);
  s := 'ABC手続 @buf ' + IntToHex(Integer(@buf),8);
  Form1.listbox1.Items.Append(s);

  len := Length(buf);  // 今のサイズを得る
  count := 0;

  // 100〜200のデータをbufにセットする処理
  for i := 100 to 200 do begin
     buf[count] := i;
     inc(count);

    // 配列の数を増やす ---------
    if count >= len then begin
      len := len + 10;    // とりあえず10増やす
      SetLength(buf,len);

      s := 'SetLength' + inttostr(len) + ' buf ' + IntToHex(Integer(buf),8);
      Form1.listbox1.Items.Append(s);
      s := 'SetLength' + inttostr(len) + ' @buf ' + IntToHex(Integer(@buf),8);
      Form1.listbox1.Items.Append(s);

    end;
    // --------------------------
  end;

  // 配列の数を実際の数に揃える
  SetLength(buf,count);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Abuf:TTestAry;
  i,len:integer;
  s:string;
begin
  // 動的配列設定
  SetLength(Abuf,10);   // とりあえず10確保

  s := 'Button1手続 SetLength Abuf ' + IntToHex(Integer(Abuf),8);
  Form1.listbox1.Items.Append(s);
  s := 'Button1手続 SetLength @Abuf ' + IntToHex(Integer(@Abuf),8);
  Form1.listbox1.Items.Append(s);

  // 手続きを呼び出す
  ABC(Abuf);

  s := '戻った Abuf ' + IntToHex(Integer(Abuf),8);
  Form1.listbox1.Items.Append(s);
  s := '戻った @Abuf ' + IntToHex(Integer(@Abuf),8);
  Form1.listbox1.Items.Append(s);

  // 結果の表示
  len := Length(Abuf);
  for i := 0 to len - 1 do begin
    ListBox1.Items.Append(inttostr(Abuf[i]));
  end;
end;


その結果、@Abuf、@bufともに常に同じアドレスを示していました。多分、
procedure TForm1.Button1Click(Sender: TObject); で定義した
var
  Abuf:TTestAry; ← のアドレスだと思います。

Abuf、bufは時々変化していました。
Abuf、bufは動的配列の先頭位置のアドレスを示している筈です。
Novさんが言われている通りSetLengthすると時々アドレスが変わっています。
多分、確保したメモリー空間で収まらなくなったので、別の広いメモリー空間に
移動した?のではないかと想像しています。

いや、そういう問題じゃなくてトラブルのもとになるようなコードを
書くべきじゃないとマスターは言われているのだと思いますが、とりあえず
この結果でこの件は自分としては納得しました。

今作成中のプログラムも「varなし」の方法に書き換えていきます。

こんな事にマスターを巻き込んでしまいスミマセンでした。


[9986] Re9:インスタンスの生成をどこでやるか返信 削除
2016/5/23 (月) 00:05:49 Nov
▼ takiさん
> 今作成中のプログラムも「varなし」の方法に書き換えていきます。

なんか言葉がいろいろ足りなくて、スミマセン。

伝えたかったのは、もっと単純な話です。
簡潔なコードをサクッと書くスキル(と根性)が足りないのでご容赦を。

(1) 動的配列をvarのない引数としてメソッドに渡す場合
  メソッド内ではローカル変数ですから、SetLengthしてアドレスが変わると、
  元の配列を参照しなくなるので、値を変更しても呼び出し元の動的配列とは
  縁が切れています。

(2) 動的配列をvarで渡す場合
  呼び出し元のメモリ再配置が起こるような状況でも、配列に対する操作が
  常に有効になるので、それはそれで意味があるコードだと思います。
  実際、SetLength自体も参照渡しですし。

まぁ、基本何でもアリかな、と思うわけです。責任は持てませんけど。

[▼次のスレッド]
INCM/CMT
Cyclamen v3.82