HSPポータル
サイトマップ お問い合わせ


HSPTV!掲示板


未解決 解決 停止 削除要請

2020
0827
こいる特定条件下でbloadすると、文字列長がバッファサイズを超えてしまう6解決


こいる

リンク

2020/8/27(Thu) 21:47:09|NO.91274

複数のテキストの中身を結合して、1つの変数にまとめたく、スクリプトを組んでいたのですが、
なんだかよく分からない現象が起きてしまいました。。。

ファイルをbload命令で読み込んだときに、なぜか文字列長がバッファサイズを超えてしまい、
バッファオーバーフローが発生してしまいます。

1時間以上、試行錯誤してみたのですが、原因が全く分かりません。
おまけに、「strlenかvarsizeを実行しないとオーバーフローしない」という意味が分からない現象も起きてしまい、
ますます、訳が分からない状態になってしました。


どなたか、原因が分かる方、なぜこんなことが起きるのか教えてください。


※以下がそのスクリプトです。

// ファイルの内容 66バイト(buf自体は64バイトだが、改行がつくので66) // なぜか64バイト以上でないと起きない(64バイトきっちりだと、1回目でオーバーフロー, 65バイト以上は4回目?) buf = {" test text test text test text test text test text test text "} notesel buf notesave "test.txt" // 読み込むファイル loadFilePath = "test.txt" sdim sFile index = 0 repeat 4 // バッファ確保 exist loadFilePath loadSize = strsize if loadSize==-1: dialog"ファイルがない": end sdim sFile_temp, loadSize; + 1 // +1するとなぜかオーバーしない // ファイル読み込み bload loadFilePath, sFile_temp // ★オーバーフローするとき、なぜか文字列長がバッファサイズを超えている // (これのせいで、バッファ拡張に使っているloadSizeと、必要サイズがずれてしまい、オーバーフローしてしまう) // なぜか、ここをコメントアウトする(strlenかvarsizeを実行しない)とオーバーしない mes "strlen:"+strlen(sFile_temp)+" varsize:"+varsize(sFile_temp) logmes sFile_temp // デバッグウィンドウのログを見ると分かるが、オーバー時に、最後辺りが文字化けしている(しかも文字化けした文字はランダム) logmes "---" // バッファ拡張 memexpand sFile, strlen(sFile) + loadSize + 1 mes "確保済み:"+varsize(sFile)+" 必要サイズ:"+str(index+strlen(sFile_temp)) // sFileに追加 poke sFile, index, sFile_temp index += strsize mes loop mes "end"



この記事に返信する


Drip

リンク

2020/8/28(Fri) 00:15:29|NO.91276

Dripです。
こいるさん、こんにちは。

そのような問題は、null(0)のない変数を文字列として扱おうとしたために発生します。
例えばa="ABC"等と書いた場合、HSPは内部的に a=65,66,67,0の4バイトを書き込みます。
これによって変数aは文字列として様々な命令を安全に扱うことができます。
しかしながら、こいるさんの書き方ではbload命令で変数に文字列を読み込んだ際、末端にnull(0)が書き込まれていません。
なのでsFile_tempを文字列として扱おうとした場合、文字列関連の命令はnullがないため変数の末端を突破してどこまでも文字列の長さを調べに行ってしまいます。
文字列をファイルから読み込む場合、必ず文字列の長さより1バイト多く確保し、null(0)を末端にセットしましょう。(「// +1するとなぜかオーバーしない」 と書かれている行で、+1したことでそこがnullとなるためこの問題が防がれます。)
通常の高級言語では内部的に変数の末端にnullを持つことで、こうしたオーバーフローの事故を未然に防いでいるのですが、初心者に優しい言語であるはずのHSPにはなぜかそのような仕組みがありません。
以前修正を提案したこともありましたが却下されてしまいました;
HSPで文字列を扱う際には必ずnullも含めて考えるようにしてください。
nullの無い文字列変数はmesで表示するのもNGです。オーバーフローが発生してしまいます。

更に注意点として、逆にsFile_tempを「保存するとき」があるとしたら、末端のnullを「含めずに」保存しましょう。
nullを含めて保存してしまうと、一部のテキストエディタはテキストファイルではなくバイナリファイルと認識してしまうことがあります。

ご注意ください。



こいる

リンク

2020/8/28(Fri) 21:59:22|NO.91283

なるほど、ありがとうございます。

しかし、strlenかvarsizeを実行しなければ、起きないのはなぜなのでしょうか?
poke命令の文をコメントアウトしても、起きないですし。
実行時に、nullを目印になにかを書き込んでるのでしょうかね……

>更に注意点として、逆にsFile_tempを「保存するとき」があるとしたら、
>末端のnullを「含めずに」保存しましょう。
はい、気を付けます。



少し別の質問になるのですが、こんなことが起きました。

変数いっぱい(null分は空ける)に文字を追加し、ファイルに出力した後に、
適当な文字列型の変数を初期化して、バッファサイズを拡張すると、
その変数の末尾当たりに、ファイル出力に使った変数に追加した文字のコードが入ってしまいます。
(デバッグウィンドウの、変数タブの「メモリダンプ」をオンにすると確認できます)

なぜこんなことが起きるのでしょうか?

※そのスクリプト

// ファイル出力 sdim buf, 100+1 repeat 100 buf += "a" loop notesel buf notesave "100byte.txt" // 適当に文字列型の変数を初期化し、バッファサイズを拡張する sdim s memexpand s, 70+1


上のスクリプトを実行すると、変数sの中身は、以下のようになります。
普通は、61の部分が00になるはずなんですが、おかしいですよね……

0000 00 00 00 00 00 00 00 00 0008 00 00 00 00 00 00 00 00 0010 00 00 00 00 00 00 00 00 0018 00 00 00 00 00 00 00 00 0020 00 00 00 00 00 00 00 00 0028 00 00 00 00 00 00 00 00 0030 00 00 00 00 00 00 00 00 0038 00 00 00 00 00 00 00 00 0040 61 61 61 61 61 61 61



MillkeyStars

リンク

2020/8/29(Sat) 11:36:04|NO.91286

memexpand は、変数の開始アドレスから要求された変数サイズでメモリを再確保します。
その際に、新たに確保された領域のデータに関しては、0 初期化を行いません。

例 : s = 8 バイト確保されている変数を 16 バイトに拡張する場合(sdim が必ず 8バイトで初期化する想定)
m [46 46 46 46 46 46 46 46 31 31 25 16 55 22 FF 55] //もともとのメモリ内容
sdim s
s [00 00 00 00 00 00 00 00] //sdim 後
memexpand s,16
s [00 00 00 00 00 00 00 00 31 31 25 16 55 22 FF 55] //memexpand 後

となります。
一つだけ例外があり、変数の開始アドレスから連続して元のサイズを含むサイズを確保できない場合は、
拡張分を含むメモリアドレスを新規に作成し、拡張前のメモリの内容を新規に作成した場所にコピーします。
その際も、拡張された部分に関しては、内容を 0 に初期化しません。
(これは HSP の仕様ではなく、実行ランタイムが依存している言語で処理されます。)

この問題を対策するには、memexpand 後に拡張された部分だけを memset などで明示的に 0 初期化を行ってください。

sdim s sLen = varsize(s) NewSize = sLen + 6 + 1 memexpand s,NewSize memset s,0,NewSize - sLen,sLen



こいる

リンク

2020/8/29(Sat) 12:19:56|NO.91287

返信ありがとうございます。

なぜ、別の変数のメモリ内容が、変数のサイズ拡張時に出てきてしまうのですか?
そういうメモリの仕組みなのでしょうか。

また、スクリプトの、notesave "100byte.txt"を実行しなければ、そういう現象は起きないのですが、
なぜnotesaveすると起きるのでしょうか?



MillkeyStars

リンク

2020/8/29(Sat) 18:04:12|NO.91288

>>なぜ、別の変数のメモリ内容が、変数のサイズ拡張時に出てきてしまうのですか?
>>そういうメモリの仕組みなのでしょうか。
そういう仕組みです。としか言えません。パフォーマンス上の問題と考えてください。

sdim a : mes "元の a : "+varptr(a) sdim s : mes "元の s : "+varptr(s) memexpand s, 64 + 7 + 1 mes "memexpand 後の s : "+varptr(s)
このように、変数のアドレスを確認する事が一番わかりやすいです。
memexpand 前のsアドレスと memexpand 後のsアドレスが違うことに注目してください。

このアドレスが再利用される場合がありますが、その際は、以前のメモリ内容がそのままになります。



こいる

リンク

2020/8/29(Sat) 18:24:38|NO.91289

>そういう仕組みです。としか言えません。パフォーマンス上の問題と考えてください。
そうですか。

とりあえず、
memexpand命令を使うときは、
拡張された部分に、memset命令で0を敷き詰めて対処したいと思います。

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


始めの質問の「strlenかvarsizeを実行しないとオーバーフローしない」という現象については、
そういう仕組みっぽく、深い意味はなさそうなので、これで解決にしたいと思います。

Dripさん、MillkeyStarsさん、お二人ともありがとうございました。



ONION software Copyright 1997-2023(c) All rights reserved.