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


HSPTV!掲示板


未解決 解決 停止 削除要請

2020
0904
とあるプログラマ文字列型変数を拡張した際の挙動について6解決


とあるプログラマ

リンク

2020/9/4(Fri) 17:11:52|NO.91309

HSP3では文字列型変数に元のサイズを超える文字列を代入しようとした際に別の内部的な変数を確保してそこに代入している ということが調べていくうちに分かったのですが、このとき古い文字列の内容含む挙動が奇妙に感じました。

というのも、まず以下のスクリプトを確認していただきたいのですが

// 初期化 sdim srcstr, 64 //再確保が行われないように64バイト未満の文字列を代入 srcstr = "Hello World" //このポインタとサイズは「Hello World」のものになる p = varptr(srcstr) s = varsize(srcstr) //再確保される長さの文字列を代入 srcstr = "このマニュアルは、HSPによるプログラミング方法と言語仕様全般を解説したものになっています。初めてプログラミングに挑戦するという人は、最初に「初心者のためのHSP入門」を 読むことをお勧めします。 " //再確保前の「Hello World」のポインタで内容を取り出す dupptr ptrstr, p, s, 2 //「このマニュアルは、HSPによる〜」ではなく「Hello World」のまま //「このマニュアルは、HSPによる〜」のポインタを得る p2 = varptr(srcstr) s2 = varsize(srcstr) //「このマニュアルは、HSPによる〜」よりも長い文字列を代入してさらに再確保させてみる srcstr = "ある程度プログラミングの経験がありHSPが初めてという方は、 このクイックスタートをお読みいただいて実際に使ってみることをお勧めします。使っている過程でわからないことが出てきたら、このマニュアルを検索したりヘルプブラウザを活用したりして 調べてみてください。" // 再確保前の「このマニュアルは、HSPによる〜」のポインタからdupptr dupptr ptrstr2, p2, s2, 2 //これは謎の内容になる //最初の「Hello World」のポインタから再度dupptr dupptr ptrstr3, p, s, 2 //これは HelloWorld のまま //srcstrに64バイト未満の文字列を代入してみる srcstr = "Hot Soup Processor" //さらにdupptr dupptr ptrstr4, p, s, 2 //「Hot Soup Processor」 にはならず Hello World のまま //(「Hot Soup Processor」が64バイト未満だからといって元々のポインタの変数に代入されることはない) dialog "待機1" // srcstrを最初のサイズを超えるサイズで初期化 sdim srcstr, 128 //この時点で ptrstr、ptrstr2、ptrstr3、ptrstr4は依然として内容が残る。 dialog "待機2" sdim srcstr, 64 //この時点で ptrstr2 以外の内容はクリアされる stop


このスクリプトにおいて最初の「Hello World」が代入された時点のポインタはその後サイズの再確保が行われても有効だということが分かります。
よって「Hello World」という文字列と「このマニュアルは、HSPによる〜」という文字列はメモリ上で干渉していないということが分かります。

しかしそれはつまり、文字列型変数のサイズの再確保が行われた際に古い内容は依然としてメモリ上には残り続けているということになると思います。
またsdim srcstr, 128でもこれらの内容はメモリ上に残り続けています。

ですが最後のsdim srcstr, 64でこれら一切の内容がクリアされています。

これらの挙動は変数を配列型にして各要素に同様の処理を行っても同じ結果となりました。



以上の点を踏まえて考察すると、

・文字列変数はsdimで初期化されたサイズを超えない文字列はそのまま代入される。

・sdimで初期化されたサイズを超える内容が代入されるとき、メモリ上に別の内部的な文字列変数を確保しそこに新規内容を代入する。
 この際元の変数そのものには処理は何も行わずに、元の変数へのアクセスは新規の内部的な変数へリダイレクトされる。

・sdimで初期化されたサイズを超える内容が内部的な変数へ代入され、さらにそれをも超える内容が代入されるとき、再度新たに内部的な変数が確保され 以前の内部的な変数は破棄される。

・sdimで最初に初期化したサイズを超えるサイズで初期化した場合、内容はメモリ上に残り続ける。

・sdimで最初に初期化したサイズ以下のサイズで初期化した場合、内容はクリアされる。

ということになると思います。
(「内部的な変数」というものは、単にCでの char *p = "文字列"; と同じことなのかもしれませんが)


私感ですが、この仕様はかなり厄介だと思います。
これってつまり、「文字列型変数は『最初に初期化したサイズ以下のサイズで再初期化する』か『最初に初期化したサイズに収まる文字列しか扱わない』以外では残骸がメモリ上に残り続ける」ということですよね?
もちろんコーディングしてる人それぞれでバラバラだと思いますが、この条件に当てはまるほどキチンとした使い方をしている人は少ないのではないでしょうか。


となると、コード中でsdimで文字列型変数を初期化する際に動的なサイズ(def(c)funcの文字列型パラメータをstrlen()するなど)で初期化するのは避けるべきな気もします。
プログラミングマニュアルやリファレンスではあらかじめsdimでサイズを指定して変数のサイズを確保を推奨していますが、もし再度sdimで初期化する際にサイズが最初に初期化したサイズを超えるようではそれでリークが発生することになります。

文字列ならまだしも、HSPはバイナリの読み込みもsdimで行うので、数メガバイトのファイルを読み込んだ場合はその分リークするということだと思います。



この記事に返信する


リンク

2020/9/5(Sat) 23:45:46|NO.91324

細かい仕様までは把握してないんですが、再確保でmallocをやりなおしてポインタを付け替えるような処理をした際、前の内容をfreeしたからといって、その領域がゼロクリアされないとかではないでしょうか?
もちろん、本当にfreeしてない可能性もあって、その場合はメモリリークが発生すると思うんですが。
sdim a,0 みたいにしたときに内容がクリアされることを考えると、やっぱりリークしてるんですかね。
自分で使っていて、確かに0バイト初期化でメモリ使用量が減ることは知っていました。
sdim test,64
repeat 10240
test+="a"
loop
とかしたら、実験できるのかな。ボタンを押したらこれが実行されて、実行前と後でメモリ使用量の差分を撮るとかで。
普通なら、10240回aを追加しているから、10KBぐらい増えるだけのはずだが、全てリークしていたらそんなもんじゃすまないという話になります。タスクマネージャーでKB単位で表示ってできたかな(win7のときはできてた)
で、これをやったあとで sdim test,0 してみるとか。



リンク

2020/9/5(Sat) 23:47:25|NO.91325

freeしたやつを再度freeすると落ちるのは知ってるけど、freeしたやつをポインタ参照した場合はどうなるのか忘れちゃいました。access violation になるのかもしれないし、ならないのかもしれない。



おにたま(管理人)

リンク

2020/9/6(Sun) 11:35:13|NO.91326

文字列変数についての検証とご指摘ありがとうございます。
もし文字列の確保と再確保だけでメモリリークが起こる状態であれば、タスクマネージャーなどでも確認できると思いますが、今のところそのような報告は頂いていません。
もし確認できるようなスクリプトがあれば、お知らせ頂けると助かります。

HSP3で、文字列を含めて変数が使用するメモリ領域の管理は、strbuf.cppソース内で行われています。
https://github.com/onitama/OpenHSP/blob/master/src/hsp3/strbuf.cpp

この中で、64byte(63文字)以内の情報は、コストの高いmalloc,freeなどの使用を避けるため、あらかじめ確保されているプール領域が使用されています。
プール領域はHSP3の起動時から終了時まで一定量確保されているためリークすることはありません。
また、64byteを超えるサイズを使用する場合はメモリの確保が行われますが、管理情報だけが変更されるので、それまで使用していたメモリの内容はクリアされません。(クリアされるのは使用を開始するメモリ領域だけになります)
dupptrは、それまで使用していたメモリ領域をポインタとして参照することのできる機能ですが、既に解放されているポインタの残骸を参照することもできてしまいます。
ですから、内容がクリアされていないことが、メモリリークであるという判断はできないかと思います。



とあるプログラマ

リンク

2020/9/6(Sun) 17:27:35|NO.91327

猫さん、おにたまさん ありがとうございます。

>>また、64byteを超えるサイズを使用する場合はメモリの確保が行われますが、管理情報だけが変更されるので、それまで使用していたメモリの内容はクリアされません。(クリアされるのは使用を開始するメモリ領域だけになります)
これは64バイトで固定なのでしょうか。それとも初期化サイズを超える場合なのでしょうか。
もし後者である場合は、「例えばsdimで32000バイトを確保している状態で48000バイト分の代入を行おうとした際は、48000バイト分の別な領域を確保し32000バイト分の領域は再度sdimで初期化するまでメモリ上に残り続ける。ここからさらに64000バイト分の代入が行われた際は、新たに64000バイト分の領域が確保され直前の48000バイト分の領域は解放されるものの、最初の32000バイト分の領域は依然として残り続けている。」という認識で間違いないでしょうか。

そしてこの認識が正しい場合、文字列型変数は初期化サイズを超える代入により残骸(初期化された領域)が再度初期化されるまでメモリ上に残ることがあるので、文字列型変数の初期化はプログラム開始時の1回のみなどではなく、ひとかたまりの処理の終了時などに随時行うことが望ましい (そして、hoge = "" よりも sdim hoge のほうが確実) という認識で問題ないでしょうか?


現在作成しているソフトがバックグラウンドで十数時間の長期的な動作を想定しているものなので、こういったメモリ周りなどの動作の安定性に関わる箇所は特に意識しており、もし執拗な態度を思わせてしまっていたら申し訳ないです…



リンク

2020/9/8(Tue) 12:43:44|NO.91329

バックグラウンドで数十時間も動作するものとは一体なにを作ってるのですか?



とあるプログラマ

リンク

2020/9/8(Tue) 14:19:47|NO.91330

>>あさん

時計アプリです。通常は非表示状態で、ホットキーを押すとポップアップするようにしています。



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