[delphi-users:3869] AnsiStringとAnsiChar配列/Byte配列との相互変換

1,182 views
Skip to first unread message

Delフサギコ

unread,
Jun 25, 2014, 12:04:19 PM6/25/14
to DelphiML
こんにちは。
文字列(AnsiString)から、AnsiChar配列、または、Byte配列との
相互変換をしたくて、下記のコードを書いています。

VCLでXE環境です。
Button1を押してCheck関数を問題なく通過するので
下記コードは問題ない様子なのですが

★マークのところをコメントアウト解除しますと
StrLCopyの所で、EAccessViolationが発生してしまいます。
(私の環境だけかもしれませんが、毎回発生します)

何かメモリを壊すような
コードになってしまっているようなのですが、
ポインタ系の処理が苦手なので、さっぱり仕組みがわかりません。

なぜエラーが起きるのか、どなたか教えていただけると助かります。
また、
文字列(AnsiString)から、AnsiChar配列、または、Byte配列との
相互変換についても、もっとこうした方がいい、とか
何か抜け落ちている処理がある、などの事ありましたら教えてください。

コードは、ここに載っていたものを参考にして書いてみました。

Byte配列→String変換
http://homepage1.nifty.com/MADIA/delphi/delphi_bbs/200609/200609_06090037.html

よろしくお願いします。

procedure VariantCheck(A, B: Variant; MessageText: String = '');
begin
if not(A = B) then
begin
Assert(False,
Trim(
'not equal' + #13#10 +
'A=' + String(A) + #13#10 +
'B=' + String(B) + #13#10 +
MessageText) ) ;
end;
end;

procedure Check(A, B: String; MessageText: String = ''); overload;
begin
VariantCheck(A, B, MessageText);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
Str1: AnsiString;
Str2: AnsiString;
ArrayAnsiChar1: array [0..1023] of AnsiChar;
ArrayAnsiChar2: array [0..4] of AnsiChar;
ArrayByte1: array [0..1023] of Byte;
ArrayByte2: array [0..4] of Byte;
begin
Str1 := 'TestString123';
Str2 := ' ';

//文字列からAnsiChar配列への変換
StrLCopy(ArrayAnsiChar1, PAnsiChar(Str1), Length(Str1));
Check('TestString123', AnsiString(ArrayAnsiChar1));

//★
// StrLCopy(ArrayAnsiChar2, PAnsiChar(Str1), Length(Str1));
// Check('TestS', AnsiString(ArrayAnsiChar2));

//AnsiChar配列から文字列への変換
SetLength(Str2, Length(ArrayAnsiChar1));
CopyMemory(PAnsiChar(Str2), @ArrayAnsiChar1, Length(ArrayAnsiChar1));
Check(Str1, FirstString(Str2, NullStr));

//文字列からByte配列への変換
StrLCopy(PAnsiChar(@ArrayByte1), PAnsiChar(Str1), Length(Str1));
Check('TestString123', AnsiString(PAnsiChar(@ArrayByte1)));

//Byte配列から文字列への変換
SetLength(Str2, Length(ArrayByte1));
CopyMemory(PAnsiChar(Str2), @ArrayByte1, Length(ArrayByte1));
Check(Str1, FirstString(Str2, NullStr));
end;

--
Delフサギコ ミ・д・彡 <delfu...@gmail.com>



MLホームページ: http://www.freeml.com/delphi-users

----------------------------------------------------------------------
家具・家電などもらえるリサイクル掲示板『ジモティー』
http://ad.freeml.com/cgi-bin/sa.cgi?id=lyQsH
------------------------------------------------------[freeml byGMO]--

7of9

unread,
Jun 25, 2014, 8:15:25 PM6/25/14
to delphi...@freeml.com
こんにちは。

XE2 Update4でも同じエラーが出ました。

コードを見直してみますと


StrLCopy(ArrayAnsiChar2, PAnsiChar(Str1), Length(Str1));
ですが、
Str1 := 'TestString123';
と10バイト以上で定義されているStr1を
ArrayAnsiChar2: array [0..4] of AnsiChar;
と5バイトで定義している所に入れようとして、おかしなことに
なっているようですね。


元々したかったことは
StrLCopy(ArrayAnsiChar2, PAnsiChar(Str2), Length(Str2));
ではないのでしょうか。



7of9
★DeNAトラベル特化バーゲン中★海外ツアー・航空券
http://ad.freeml.com/cgi-bin/sa.cgi?id=lySSM
・【ツアー】6-9月発 ソウル3日間 1.29万円~
・【航空券】6-9月発 成田⇔ソウル 0.35万円~
・【航空券】6-9月発 成田⇔ヨーロッパ2.02万円~
------------------------------------------------------[freeml byGMO]--

7of9

unread,
Jun 25, 2014, 8:26:31 PM6/25/14
to delphi...@freeml.com
ちなみに、EAccessViolationが発生していたところは
SetLength(Str2, Length(ArrayAnsiChar1));
でした。

元々のコードで、//★をアンコメントした状態で実行し、
上記の行でブレークすると、Str2を デバッグ > インスペクト
しようとしても「インスペクトエラー 'Str2':参照できない値です」
というエラーがでました。


var
Str1: AnsiString;
Str2: AnsiString;
ArrayAnsiChar1: array [0..1023] of AnsiChar;
ArrayAnsiChar2: array [0..4] of AnsiChar;
ArrayByte1: array [0..1023] of Byte;
ArrayByte2: array [0..4] of Byte;
と定義されている状態で、ArrayAnsiChar2にStrLCopyで1024個の
文字を入れてしまったがために、(おそらく)ArrayAnsiChar1、
Str1, Str2の領域が破壊され、Str2のアドレスがおかしな状態に
なった上で
SetLength(Str2, Length(ArrayAnsiChar1));
としてStr2を使おうとしたのでEAccessViolationが発生したという気がします。


メモリ配置については記憶が定かではありませんが、5バイトで定義して
た変数に10バイトなどの書込んでしまうと、上側に宣言した変数の
領域を破壊したかと思います。

var
ArrayAnsiChar1: array[0..5] of AnsiChar;
ArrayAnsiChar2: array[0..5] of AnsiChar;
ArrayAnsiChar3: array[0..5] of AnsiChar;
でArrayAnsiChar2に10バイトを入れようとするとArrayAnsiChar3の
領域でなくArrayAnsiChar1の領域が破壊されるのだったと思います。




7of9
自分に合った趣味イベントに参加して新しい仲間を見つけませんか?
■■ freeml姉妹サイト IRORI(イロリ) ■■
http://ad.freeml.com/cgi-bin/sa.cgi?id=lyTde
------------------------------------------------------[freeml byGMO]--

Delフサギコ

unread,
Jun 26, 2014, 12:33:55 PM6/26/14
to DelphiML
こんにちは。返事遅くてすいません。

試していただきまして、ありがとうございます。
自分にとっては非常になぞな挙動でしたので、情報すごく助かります。

> StrLCopy(ArrayAnsiChar2, PAnsiChar(Str1), Length(Str1));
> ですが、
> Str1 := 'TestString123';
> と10バイト以上で定義されているStr1を
> ArrayAnsiChar2: array [0..4] of AnsiChar;
> と5バイトで定義している所に入れようとして、おかしなことに
> なっているようですね。

なるほど!これがヤバイのですね。

> 元々したかったことは
> StrLCopy(ArrayAnsiChar2, PAnsiChar(Str2), Length(Str2));
> ではないのでしょうか。

いえ、ArrayAnsiChar2は5バイト分しかないので
Str1の先頭5バイト分を代入したかったつもりだったのです。

十分に大きいサイズのAnsiChar配列と
小さいサイズのAnsiChar配列、
どちらにも文字列を代入してちゃんと動くように試しておきたかったのです。

> ちなみに、EAccessViolationが発生していたところは
> SetLength(Str2, Length(ArrayAnsiChar1));
> でした。

そうでした。
//★の部分ではStr2をさわっていないのに
その後の
SetLength(Str2, Length(ArrayAnsiChar1));
でエラーが出るのが納得いかなかったのですが
やはり変数領域を破壊しているのですね。


さらに動作確認コードを書いてみました。
うまくいくかな...と思ったら、イベント内は問題ないのですが
アプリケーション終了時にEAccessViolationエラーが出ます。

やはり、何らかのメモリ破壊してしまっているのですね。

うーん、難しいです。

procedure VariantCheck(A, B: Variant; MessageText: String = '');
begin
if not(A = B) then
begin
Assert(False,
Trim(
'not equal' + #13#10 +
'A=' + String(A) + #13#10 +
'B=' + String(B) + #13#10 +
MessageText) ) ;
end;
end;

procedure Check(A, B: String; MessageText: String = ''); overload;
begin
VariantCheck(A, B, MessageText);
end;

procedure TForm1.Button2Click(Sender: TObject);
var
SourceStr: AnsiString;
DestStr: AnsiString;
BufferStr: AnsiString;
ArrayAnsiChar1: array [0..1023] of AnsiChar;
ArrayAnsiChar2: array [0..4] of AnsiChar;
begin
SourceStr := 'TestString123';
DestStr := '';

begin
//文字列からAnsiChar配列への変換
BufferStr := SourceStr;
if Length(BufferStr) >= Length(ArrayAnsiChar1) then
begin
SetLength(BufferStr, Length(ArrayAnsiChar1));
end;
StrLCopy(ArrayAnsiChar1, PAnsiChar(BufferStr), Length(BufferStr));
Check('TestString123', AnsiString(ArrayAnsiChar1));
end;

//文字列からAnsiChar配列への変換
begin
BufferStr := SourceStr;
if Length(BufferStr) >= Length(ArrayAnsiChar2) then
begin
SetLength(BufferStr, Length(ArrayAnsiChar2));
end;
StrLCopy(ArrayAnsiChar2, PAnsiChar(BufferStr), Length(BufferStr));
Check('TestS', AnsiString(ArrayAnsiChar2));
end;
end;


※先に出していたコードは下記の定数定義と関数が抜けていました。
ご参考くださればと思います。
const
NullStr = #$0;//#0

function FirstString(s: AnsiString; Delimiter: AnsiString): AnsiString;
var
DeleteIndex: Integer;
begin
Result := s;
DeleteIndex := Pos(Delimiter, s);
if DeleteIndex = 0 then Exit;

// Result := copy( Result, 1, DeleteIndex - 1);
Delete(Result, DeleteIndex, MaxInt);
end;

2014年6月26日 9:26 7of9 <delphi...@freeml.com>:
--
--
Delフサギコ ミ・д・彡 <delfu...@gmail.com>



MLホームページ: http://www.freeml.com/delphi-users

----------------------------------------------------------------------
家具・家電などもらえるリサイクル掲示板『ジモティー』
http://ad.freeml.com/cgi-bin/sa.cgi?id=lzh2y
------------------------------------------------------[freeml byGMO]--

7of9

unread,
Jun 26, 2014, 8:52:51 PM6/26/14
to delphi...@freeml.com
こんにちは。7of9です。


Button2Clickイベントですが、確かにこちら(XE2 + Update4)でも再現しました。
再現手順としては、Button2をクリックしつづけて、18回目のクリック時に
「無効なポインタ操作」と表示されることがわかりました。
(18回目というのは、適当にブレークポイントを設定し、ブレークポイントの
「パスカウント」を1000にすることにより、イベントログウィンドウに
「通過回数 18/1000」のように表示されることで確認しました)。


コードを見直してみて、ちょっと気になる部分がありました。

それは、2つ目の文字列比較において、文字列の最後にあるべき「#0」を考慮していない
ことです。

ArrayAnsiChar2: array [0..4] of AnsiChar; // original
は'TestS'という5バイトの比較を行うので、実際には#0を足した
6バイトで宣言しておく必要があるように思います。
ArrayAnsiChar2: array [0..5] of AnsiChar; // 'TestS' + #0 のため6バイト必要


また、
SetLength(BufferStr, Length(ArrayAnsiChar2)); // original
する時は、ArrayAnsiChar2の長さでSetLengthしてしまうと、#0を足した分の
長さでSetLengthしてしまい、'TestS'でなく'TestSt'までがBufferStr
の文字列となってしまい、Check()に失敗します。
なので、この場合は、-1をした長さでSetLengthするのが正しい
ように思います。
SetLength(BufferStr, Length(ArrayAnsiChar2) - 1); // #0を引いた分

あとは、念のためにArrayAnsiChar2の最後に#0を入れる処理を追加しました。
lastIdx := Length(ArrayAnsiChar2) - 1; // 追加した
ArrayAnsiChar2[lastIdx] := #0; // 追加した (念のため)
ここで、lastIdxはinteger型の変数です。


上記の修正を行うことで、Button2をクリックし続けても18回目にエラーが
出ることがなくなりました。アプリ終了時にも問題ないと思います。


Button2Clickの書き直したのは以下の通りです。
ArrayAnsiChar1の処理は修正してないので、同じようにしてください。

.....................

procedure TForm1.Button2Click(Sender: TObject);
var
SourceStr: AnsiString;
DestStr: AnsiString;
BufferStr: AnsiString;
ArrayAnsiChar1: array [0..1023] of AnsiChar;
// ArrayAnsiChar2: array [0..4] of AnsiChar; // original
ArrayAnsiChar2: array [0..5] of AnsiChar; // 'TestS' + #0 のため6バイト必要
lastIdx: integer; // 追加した
begin
SourceStr := 'TestString123';
DestStr := '';

begin
//文字列からAnsiChar配列への変換
BufferStr := SourceStr;
if Length(BufferStr) >= Length(ArrayAnsiChar1) then
begin
SetLength(BufferStr, Length(ArrayAnsiChar1));
end;
StrLCopy(ArrayAnsiChar1, PAnsiChar(BufferStr), Length(BufferStr));
Check('TestString123', AnsiString(ArrayAnsiChar1));
end;

//文字列からAnsiChar配列への変換
begin
BufferStr := SourceStr;
if Length(BufferStr) >= Length(ArrayAnsiChar2) then
begin
// SetLength(BufferStr, Length(ArrayAnsiChar2)); // original
SetLength(BufferStr, Length(ArrayAnsiChar2) - 1); // #0を引いた分
end;
StrLCopy(ArrayAnsiChar2, PAnsiChar(BufferStr), Length(BufferStr));
lastIdx := Length(ArrayAnsiChar2) - 1; // 追加した
ArrayAnsiChar2[lastIdx] := #0; // 追加した (念のため)
★DeNAトラベル特化バーゲン中★海外ツアー・航空券
http://ad.freeml.com/cgi-bin/sa.cgi?id=lzkHw

Delフサギコ

unread,
Jun 29, 2014, 12:11:01 PM6/29/14
to DelphiML
夜分になってしまいすいません。
非常に詳細に追試していただいてありがとうございます。

すばらしいです!
丁寧なアドバイスに
とてもとても学ばせてもらい、助かりました。

下記のButton3Clickを作って動かしてみて、

AnsiStringToArrayAnsiCharと
Button4Clickで、汎用化しておきました。

お世話になりました。

procedure TForm1.Button3Click(Sender: TObject);
var
SourceStr: AnsiString;
ArrayAnsiChar1: array [0..1023] of AnsiChar;
ArrayAnsiChar2: array [0..5] of AnsiChar;
BufferStr: AnsiString;
BufferSize: Integer;
begin
SourceStr := 'TestString123';

//文字列からAnsiChar配列への変換
begin
BufferSize := Length(ArrayAnsiChar1);
BufferStr := SourceStr;
if (BufferSize - 1) < Length(BufferStr) then
begin
SetLength(BufferStr, BufferSize - 1);
end;
StrLCopy(ArrayAnsiChar1, PAnsiChar(BufferStr), BufferSize - 1);
ArrayAnsiChar1[ BufferSize - 1 ] := #0;
Check('TestString123', AnsiString(ArrayAnsiChar1));
end;

//文字列からAnsiChar配列への変換
begin
BufferSize := Length(ArrayAnsiChar2);
BufferStr := SourceStr;
if (BufferSize - 1) < Length(BufferStr) then
begin
SetLength(BufferStr, BufferSize - 1);
end;
StrLCopy(ArrayAnsiChar2, PAnsiChar(BufferStr), BufferSize - 1);
ArrayAnsiChar2[ BufferSize - 1 ] := #0;
Check('TestS', AnsiString(ArrayAnsiChar2));
end;
end;

procedure AnsiStringToArrayAnsiChar(Str: AnsiString; var ArrayValue:
array of AnsiChar);
var
BufferSize: Integer;
I: Integer;
begin
BufferSize := Length(ArrayValue);
if (BufferSize - 1) < Length(Str) then
begin
SetLength(Str, BufferSize - 1);
end;
StrLCopy(ArrayValue, PAnsiChar(Str), BufferSize - 1);
ArrayValue[ BufferSize - 1 ] := #0;
end;

procedure TForm1.Button9Click(Sender: TObject);
var
SourceStr: AnsiString;
ArrayAnsiChar1: array [0..1023] of AnsiChar;
ArrayAnsiChar2: array [0..5] of AnsiChar;
begin//
SourceStr := 'TestString123';
AnsiStringToArrayAnsiChar(SourceStr, ArrayAnsiChar1);
Check('TestString123', AnsiString(ArrayAnsiChar1));

SourceStr := 'TestString123';
AnsiStringToArrayAnsiChar(SourceStr, ArrayAnsiChar2);
Check('TestS', AnsiString(ArrayAnsiChar2));
end;

2014年6月27日 9:52 7of9 <delphi...@freeml.com>:
--
--
Delフサギコ ミ・д・彡 <delfu...@gmail.com>



MLホームページ: http://www.freeml.com/delphi-users

----------------------------------------------------------------------
自分に合った趣味イベントに参加して新しい仲間を見つけませんか?
■■ freeml姉妹サイト IRORI(イロリ) ■■
http://ad.freeml.com/cgi-bin/sa.cgi?id=lzUpj
------------------------------------------------------[freeml byGMO]--
Reply all
Reply to author
Forward
0 new messages