ポインタに代入した時のアドレス

解決


ひよこ  2001-10-05 01:40:58  No: 196  IP: [192.*.*.*]

はじめまして。
早速ですが、ポインタへの代入でおかしなことがあるのです。

var
  str : String;
  buf : PChar;
begin
  str := 'abcdefghij';
  buf := PChar(str);
  buf[1] := '1';
  buf[3] := '3';
end;

としても、strの文字列が変更されません。
ポインタに代入すると、strとbufのアドレスは同じになると思っていたのですが、

  ListBox1.Items.Add(IntToHex(Integer(strTemp), 8));
  ListBox1.Items.Add(IntToHex(Integer(buf), 8));

で確認すると、アドレスが全然違っています。 これは一体どういうことなんでしょうか?

編集 削除
ひよこ  2001-10-05 01:53:10  No: 197  IP: [192.*.*.*]

すみません。質問でちょっと書き間違えがありました。

ListBox1.Items.Add(IntToHex(Integer(strTemp), 8));
ListBox1.Items.Add(IntToHex(Integer(buf), 8));

で、strTempは strの間違いです。

ListBox1.Items.Add(IntToHex(Integer(str), 8));
ListBox1.Items.Add(IntToHex(Integer(buf), 8));

この2つのアドレスが同じにならないのはどうしてなんでしょう。

編集 削除
nic  2001-10-05 11:03:55  No: 198  IP: [192.*.*.*]

文字列をバイナリ形式で読み書きするには?
の現象と同じようです。

編集 削除
ひよこ  2001-10-07 08:57:23  No: 199  IP: [192.*.*.*]

nicさん、回答ありがとうございました。
いろいろテストしてみたのですが、

procedure TForm1.Button1Click(Sender: TObject);
var
str : String;
buf : PChar;
begin
str := 'abcdefghij';
str[1] := str[1];    // ダミーのコード
buf := PChar(str);
buf[1] := '1';
buf[3] := '3';

// 確認用にそれぞれの値を表示させる
ListBox1.Items.Add(IntToHex(Integer(str), 8));
ListBox1.Items.Add(IntToHex(Integer(buf), 8));
ListBox1.Items.Add('str[1]='+ str[1]);
ListBox1.Items.Add('buf[1]='+ buf[1]);
ListBox1.Items.Add(str);
ListBox1.Items.Add(buf);
end;

上のように、ダミーの
str[1] := str[1];
を追加してみたら、strとbufの値(アドレス)が同じになりました。
また、「var str: String;」の定義を関数の外に出しても、strとbufの値は同じになります。
これは、Delphiの仕様なんでしょうか?(Delphi6.0 Personal版)
まさかDelphiのバグなんてことはないでしょうね。(^^;;

strとbufの値(アドレス)が同じであっても、str[1]とbuf[1]の値が違うことも要注意です。
String(長い文字列)をポインタで操作するなんてことは規則違反なんでしょうか?

話が変わりますが、ここに書き込むと行の先頭のスペースが消えてしまいます。
先頭のスペースを有効にするにはどうしたらいいんでしょう?

編集 削除
PAPY  2001-10-07 13:22:49  No: 200  IP: [192.*.*.*]

>buf := PChar(str);

この部分でString型のStrのポインタをもらっているので
アドレスは同じになると思います。
また、
str[1] := str[1]; // ダミーのコード
を削除してもアドレスは同じでした。


>話が変わりますが、ここに書き込むと行の先頭のスペースが消えてしまいます。
先頭のスペースを有効にするにはどうしたらいいんでしょう?

私(管理人)もわかりません(^^;
とりあえず、「>」を先頭にいれれば色が変わります。

編集 削除
PAPY  2001-10-07 13:29:36  No: 201  IP: [192.*.*.*]

テスト環境は
Delphi5 pro
Delphi6 無料版です。

※リソースが激減しているときなどは微妙なエラーが発生することがあるので
  その手のものではないでしょうか?

編集 削除
ひよこ  2001-10-09 01:40:07  No: 202  IP: [192.*.*.*]

PAPYさん、始めましてm(__)m
前に書いたサンプルでは、短くするために無関係と思われる部分を
省略してしまったので、現象がうまく再現されませんでした。

実際に試したプログラムでは、
buf := PChar(str);
の後で下のような「strの要素にアクセスするコード」が入っていました。
ListBox1.Items.Add(IntToHex(Integer(@str[1]), 8));

つまり、buf := PChar(str) を実行した段階では bufとstrの値は同じになりますが、このコードを実行した後では strのアドレスが変わっていたのです。

$00462FB4 ← 最初のアドレス
$00BE08D4 ← 変わった後のアドレス

なぜアドレスが変わってしまうのか不思議ですが、変わった後の方が正しい
アドレスのようです。

以下のプログラムでその現象が確認出来るはずなんですが...

procedure TForm1.Button1Click(Sender: TObject);
var
  str: string;
  buf: PChar;
begin
  str := 'abcdefghij';
  buf := PChar(str);      // ここで bufとstrの値は同じになるが...
  try
  buf[1] := '1';          // ここで書き込みエラーが発生するので
  buf[3] := '3';          // bufの値(アドレス) が不正と思われる
  finally
  ListBox1.Clear;
  // str と buf の値(アドレス)を確認
  ListBox1.Items.Add(IntToHex(Integer(str), 8));
  ListBox1.Items.Add(IntToHex(Integer(buf), 8));
  // このコードでstrのアドレスが変わってしまう(ナゼ?)
  ListBox1.Items.Add(IntToHex(Integer(@str[1]), 8));
//  str[1] := str[1];       // これでもstrのアドレスが変わる(ナゼ?)
  buf := PChar(str);      // また bufに strの値を代入する
  buf[1] := '1';          // 今度はエラーにならない
  buf[3] := '3';
  // 再確認してみると str の値(アドレス)が変わっている
  ListBox1.Items.Add(IntToHex(Integer(str), 8));
  ListBox1.Items.Add(IntToHex(Integer(buf), 8));
  ListBox1.Items.Add('str[1]='+ str[1]);
  ListBox1.Items.Add('buf[1]='+ buf[1]);
  ListBox1.Items.Add(str);
  ListBox1.Items.Add(buf);
  end;
end;

編集 削除
PAPY  2001-10-09 14:56:02  No: 203  IP: [192.*.*.*]

4回目の投稿記事より
>strとbufの値(アドレス)が同じであっても、str[1]とbuf[1]の値が違うことも要注意です。

String型の配列は初期値が"1"なので"1"で良いですが、Pchar型の配列は"0"が初期値です。
ListBox1.Items.Add('strの1番目の値='+ str[1]);
ListBox1.Items.Add('bufの1番目の値='+ buf[0])

前回の投稿記事より
>buf[1] := '1'; // ここで書き込みエラーが発生するので
>buf[3] := '3'; // bufの値(アドレス) が不正と思われる

D5/D6+Win98ではエラーは発生していません(^^;

>ListBox1.Items.Add(IntToHex(Integer(@str[1]), 8));

問題はこのコードですね。
確かにこのコードをいれるとstrのアドレスが変更されてしまいますね。

String自体がポインタの塊なので、配列によるアドレス参照を行うと
アドレスが変わってしまう仕組みがあるのかもしれません。
String型を形成しているソースがないのでよくわかりませんが・・・

※この件に関しては私にもよくわかりません。お役に立てなくてごめんさいm(- -)m

編集 削除
nic  2001-10-16 09:49:07  No: 204  IP: [192.*.*.*]

absolute 指令って使えるかな(未確認)

編集 削除
dil  2001-10-25 03:31:04  No: 205  IP: [192.*.*.*]

はじめまして。

stringは、メモリの節約のため、普段は参照カウントを使って同じメモリを共有しています。例えば、

var a,b : string;

a := 'Sample';
b := a;

などというコードを書くと、コンパイラはbに対してaのコピーを作成するのではなく、a の  stringのポインタを参照させます。
(なんとなくstringってクラスみたいな感じですね)


それで、bに対して変更を行ったときに初めて
UniqueStringAなる関数を持ち出して、
aのコピーを作成、bに渡したのち、そのメモリに対して変更を行います。
(ただこの関数はコンパイラ内部でしか使えないようです)

それでは、コンパイラがどう言ったときにメモリの参照を変更するのか、
ということが非常に問題になってくるわけですが、
string を PCharにキャストしただけではメモリの参照は変わりません。
コンパイラはキャスト時に、StringToPCharという関数…

関数!?

…を使用してポインタの値を取得します。
(まさか型キャストにコンパイラが内部で関数を呼び出しているとは今日まで知りませんでしたが…)

問題はストリングに対してアドレス計算をした時で、まあ…たいていアドレス確保した、ということは変更するつもり…ということなんでしょうか、コンパイラはここで別のメモリを割り当てて、参照解除してしまいます。
データが更新されない、メモリが異なる、というのはそのためです。

アクセス違反については私の環境(Del6Pro)でも確認できなかったため、
なんとも言えませんが、ブレークポイントをかけてCPUViewを覗いて調べてみることをお勧めします。

P := PChar(str);

P := @str[1];

一見同じコードを生成しそうなものですけどねぇ…(^^;
とにかくstringの演算はユーザのメモリ管理を簡単にするため、
:= や @ が「嘘」の演算をしている感じです。
C++ユーザーさんだったら普通に感じるのかなぁ…
私はDelphiばかり使ってたので…。

編集 削除
ひよこ  2001-10-28 23:57:21  No: 206  IP: [192.*.*.*]

すみません。返事が大変遅くなってしまいました。
皆様回答ありがとうございました。

Delphiの長い文字列というのは足し算が出来て便利なのですが、内部では複雑なことが行われているんですね。

書き込みエラーが出ないということですが、こちらでは以下のようなエラーが出ます。

「モジュール'TEST.EXE'のアドレス 00462DC5 でアドレス 00462FC9 に対する書き込み違反が起きました。」

最初のうちはこちらでもエラーは出なかったのですが、いろいろコードをいじっているうちに出るようになってしまいました。
ただし、Delphiの開発環境中で実行してエラーが出なくても、単独で exe を実行すると必ずエラーが出ます。
Delphi開発環境中ではメモリの使用状況によってエラーが出たり出なかったりするのかも知れません。

エラーが発生するアドレスを CPUウィンドウで確認したところ、プログラムコード中に埋め込まれた文字列「abcdefghj」を書き換えるところで「書き込み違反」になっていることが分かりました。
プログラムコード中に埋め込まれた文字列を書き換える場合は、その文字列をヒープ領域にコピーするのがDelphiの文字列の仕様なんですね。
プログラムコードは書き換えられないようにプロテクトされているということでしょうか?
ポインタを使用して文字列を書き換える場合はそのプロテクトされたメモリ領域を強引に書き換えることになってしまうので、エラーが出ると考えていいのでしょうか?

編集 削除
dil  2001-10-29 05:41:37  No: 207  IP: [192.*.*.*]

>単独で実行
確かにそのとおりでした。
バージョン依存でこちらではエラーが出ないのか…と思っていたのですが、
デバッグではなく、Windowsから単独で実行するとエラーが出ました。

まさかデバッグで起きないことが単独では出るとは思ってませんでした。
私はWindowsのシステムに詳しくないのでよくわかりませんが、
ひよこさんの考えられている、コードの書き換え失敗によるエラーという感じが強そうな気がします。

>プログラムコードは書き換えられないようにプロテクトされているということでしょうか?

そういうことなのでしょうね。しかし、自分でメモリを確保して、そこに実行コードを書きこんでいくことは可能なようで、別にWrite属性さえあればExecute属性をつける必要は無いようです。
const C_InstancePageSize = 4096;
var P : PByte; Proc : procedure;
begin
  P := VirtualAlloc(nil, C_InstancePageSize,
                  MEM_COMMIT,PAGE_READWRITE); // GetMemでもOK?
  P^ := $C3; // RET
  Proc := Pointer(P);
  Proc;
end;
は動きますし…。

一応解決策の1つとして、上に挙げた @str[1] をポインタの取得に使ってみてはどうでしょうか。これなら単独でも問題無く動きました。

編集 削除
ひよこ  2001-11-02 00:12:59  No: 208  IP: [192.*.*.*]

また返事が遅れてしまいましたが、
>一応解決策の1つとして、上に挙げた @str[1] をポインタの取得に使ってみてはどうでしょうか。
その方法でエラーは出なくなりました。(^^)
buf := @str[1];
とすれば、buf の値がコピーされた方の文字列の先頭アドレスになるんですね。

話が難しい方向に変わってしまいましたが、文字列についての理解を深めることが出来て大変感謝しています。
本当にありがとうございました。

編集 削除