a26_txt03_001
の編集
https://essen.osask.jp/?a26_txt03_001
[
トップ
] [
編集
|
差分
|
バックアップ
|
添付
|
リロード
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
-- 雛形とするページ --
BracketName
EssenRev4
FormattingRules
FrontPage
Help
InterWiki
InterWikiName
InterWikiSandBox
K
MenuBar
PHP
PukiWiki
PukiWiki/1.4
PukiWiki/1.4/Manual
PukiWiki/1.4/Manual/Plugin
PukiWiki/1.4/Manual/Plugin/A-D
PukiWiki/1.4/Manual/Plugin/E-G
PukiWiki/1.4/Manual/Plugin/H-K
PukiWiki/1.4/Manual/Plugin/L-N
PukiWiki/1.4/Manual/Plugin/O-R
PukiWiki/1.4/Manual/Plugin/S-U
PukiWiki/1.4/Manual/Plugin/V-Z
RecentDeleted
SDL2_01
SandBox
WikiEngines
WikiName
WikiWikiWeb
YukiWiki
a21
a21_acl01
a21_bbs01
a21_challengers
a21_count
a21_edu01
a21_edu02
a21_edu03
a21_edu04
a21_edu05
a21_edu06
a21_edu07
a21_edu08
a21_edu09
a21_edu10
a21_edu11
a21_hlx000
a21_hlx001
a21_hlx001_1
a21_hlx001_2
a21_hlx001_3
a21_hlx002
a21_hlx002_1
a21_hlx003
a21_hlx003_1
a21_hlx004_1
a21_memo01
a21_opt
a21_opt02
a21_opt03
a21_p01
a21_special
a21_tl9a
a21_todo
a21_txt01
a21_txt01_10
a21_txt01_1a
a21_txt01_2
a21_txt01_2a
a21_txt01_2b
a21_txt01_3
a21_txt01_4
a21_txt01_5
a21_txt01_6
a21_txt01_6a
a21_txt01_7
a21_txt01_8
a21_txt01_8a
a21_txt01_9
a21_txt01_9a
a21_txt02
a21_txt02_10
a21_txt02_10a
a21_txt02_10b
a21_txt02_11
a21_txt02_11a
a21_txt02_12
a21_txt02_12a
a21_txt02_12b
a21_txt02_1a
a21_txt02_1b
a21_txt02_2
a21_txt02_2a
a21_txt02_3
a21_txt02_3a
a21_txt02_4
a21_txt02_4a
a21_txt02_5
a21_txt02_5a
a21_txt02_6
a21_txt02_6a
a21_txt02_6b
a21_txt02_6b_rev0
a21_txt02_6x
a21_txt02_7
a21_txt02_7a
a21_txt02_8
a21_txt02_8a
a21_txt02_9
a21_txt02_9a
a22_acl2_01
a22_acl2_02
a22_edu12
a22_intro01
a22_intro02
a22_intro03
a22_memman01
a22_memman02
a22_memman03
a22_memman04
a22_memman05
a22_memman06
a22_memman07
a22_memo01
a22_mingw_debug
a22_txt03
a22_txt03_1a
a22_txt03_1b
a22_txt03_2
a22_txt03_2a
a22_ufcs01
a23_bbs
a23_ec001
a23_ec002
a23_intro00
a23_intro000
a23_intro01
a23_intro02
a23_intro03
a23_intro04
a23_intro05
a23_intro06
a23_intro07
a23_intro08
a23_intro09
a23_intro10
a23_intro10wk1
a23_intro10wk2
a23_intro10wk3
a23_intro11
a23_intro12
a23_intro13
a23_intro13wk1
a23_intro14
a23_intro15
a23_intro16
a23_intro17
a23_intro17wk1
a23_intro18
a23_intro19
a23_intro90
a23_intro91
a23_neopixel1
a23_os01
a23_useSelfMade
a23_usm001
a23_usm002
a23_usm003
a23_usm004
a23_usm005
a23_usm006
a23_usm007
a23_usm008
a23_usm009
a24_AMap11
a24_AMapSim11
a24_AMemFile
a24_AMemMan
a24_aErrExit
a24_aFnv
a24_aOsFunc
a24_aQSort
a24_aXorShift32
a24_acl1T_doc01
a24_acl1Tiny
a24_acpp0
a24_buntan01
a24_cMin
a24_getTyp
a24_goodvalues
a24_idea001
a24_longdef
a24_memo01
a24_memo02
a24_osc20240310
a24_osc20241026
a24_picoLcd13
a24_picoTrain1
a24_programs
a24_raspberrypi01
a24_raspberrypi02
a24_schedule
a24_spc2tab
a24_tab2spc
a24_useSelfMade
a25_acl3
a25_acl4
a25_acl4_001
a25_acl4_002
a25_acl4_003
a25_acl4_log01
a25_acl4_log02
a25_acl4_log03
a25_buntan02
a25_buntan03
a25_buntan04
a25_buntan05
a25_kcas01
a25_kharc01
a25_kharc02
a25_kharc03
a25_kharc04
a25_kharc05
a25_kharc06
a25_kharcs1
a25_kharcs2
a25_kharcs3
a25_kharcs4
a25_kharcs5
a25_kharcs6
a25_kharcs7
a25_kharcs8
a25_kharcs9
a26_txt03
a26_txt03_001
a26_txt03_002
a4_0001
a4_0002
a4_0003
a4_0004
a4_0005
a4_0006
a4_0007
a4_0008
a4_0009
a4_0010
a4_0011
a4_0012
a4_0013
a4_0014
a4_0016
a4_0017
a4_0018
a4_0019
a4_0020
a4_0021
a4_0022
a4_0023
a4_comments
a4_d0001
a4_d0002
a4_d0003
a4_d0004
a4_d0005
a4_d0006
a4_i01
a4_links
a4_log
a4_log00
a4_log04
a4_log05
a4_log06
a4_log07
a4_log08
a4_log09
a4_log10
a4_log11
a4_p0001
a4_p0002
a4_p0003
a4_p0004
a4_p0005
a4_p0006
a4_p0007
a4_p0008
a4_t0002
a4_t0003
aclib00
aclib01
aclib02
aclib03
aclib04
aclib05
aclib06
aclib07
aclib08
aclib09
aclib10
aclib11
aclib12
aclib13
aclib14
aclib15
aclib16
aclib17
aclib18
aclib19
aclib20
aclib21
aclib22
aclib23
aclib24
aclib25
aclib_bbs
arm64_01
avm0001
edu0001
edu0002
edu0003
esb02b_hrb
esb_dbg
esbasic0001
esbasic0002
esbasic0003
esbasic0004
esbasic0005
esbasic0006
esbasic0007
esbasic0008
esbasic0009
esbasic0010
esbasic0011
esbasic0012
esbasic0013
esbasic0014
esbasic0015
esbasic0016
esbasic0017
esbasic02a
esc0001
escm0001
essen_hist
esvm0001
esvm0002
esvm0003
esvm0004
esvm0005
esvm0006
esvm_i0
hh4a
idea0001
idea0002
idea0003
impressions
jck_0000
jck_0001
kawai
kbcl0_0000
kbcl0_0001
kbcl0_0002
kbcl0_0003
kbcl0_0004
kbcl0_0005
kbcl0_0006
kbcl0_0007
kclib1_0000
kclib1_0001
kclib1_0002
kclib1_0003
kclib1_0004
kclib1_0005
kclib1_0006
kclib1_0007
kclib1_0008
kclib1_0009
kclib1_0010
kpap0001
members
memo0001
osask4g
osask4g_r2
p20200311a
p20200610a
p20200610b
p20200624a
p20200711a
p20200716a
p20250813a
p20250813b
p20250813c
p20250815a
p20250903a
p20251006a
page0001
page0002
page0003
page0004
page0005
page0006
page0007
page0008
page0009
page0010
page0011
page0012
page0013
page0014
page0015
page0016
page0017
page0018
page0019
page0020
page0021
page0022
page0023
populars
seccamp
seccamp2019
sechack
sechack2019
seclang01
sh3_2020
sh3_2020_kw
sh3_2020_nk
sh3_2021_kw
sh3_2021_nk
sh3_2022_kw
sh3_2023_kw
sh3_2024_kw
sh3_2025_kw
sh3_2026_kw
sh3_kw_hist
termux001
termux002
text0001
text0001a
text0002
text0002a
text0003
text0004
text0005
text0006
text0006a
text0007
text0008
text0010
text0011
text0012
text0013
text0014
text0015
text0016
text0017
text0018
text0019
text0020
text0021
tl1c
tl2c
tl3c
tl3d
* 川合のプログラミング言語自作のためのテキスト第四版#001 -(by [[K]], 2026.05.28) --目次はこちらです。→[[a26_txt03]] ** (1) デバッグレベル -acl4v2 には「デバッグレベル」という概念があります。 -このデバッグレベルは、コンパイラの最適化レベルのことだろうと誤解されることがあるのですが、それは違います。・・・紛らわしいのは「デバッグ時は最適化レベルを最低にしましょう」という正しいアドバイスがあって、それでデバッグと最適化レベルが結びついているせいだと思います。 -コンパイラの最適化は、生成する機械語の質を上げるためなら、プログラムに書かれた通りには処理しないで、あえて順序を変えたり共通化部分をまとめたりします(でも実行結果は変わりません)。こういうことをされると、変数〇〇に3が代入されるところからトレースしようとか思っても、デバッグがうまくいかないのです。コンパイラはその変数の更新タイミングを保持しなければいけないとは気づけないので、順序入れ替えなどをやってしまっているせいです。・・・だからデバッグ時は最適化をできるだけしないようにするわけです。・・・なお、printfでデバッグをする場合は、printf結果が変わってしまうと実行結果を変えたことになってしまうので、最適化レベルがmaxであっても支障はありません(最適化レベルが高いとコンパイルに時間がかかるという問題はありますが)。 -acl4v2 のデバッグレベルは最適化とは関係なくて、「このプログラムはまだバグがあるかもしれないから、とにかく用心深くチェックしながら実行してね」という意味です。つまりデバッグレベルが5のときでも、最適化レベルを最高にして構いませんし、デバッグレベルが0のときに最適化レベルを最低にしてもかまいません。速いか遅いかというよりも、バグチェックをしながら実行するかどうかの違いです(まあチェックをすれば基本的には少し遅くなりますが)。 -デバッグレベルによるチェック強化は、すべて実行時チェックによるものなので、コンパイル時に追加でエラーが見つかるということはありません(残念ながら)。 -デバッグレベルは a_DbgLv をdefineして指定します(ライブラリのincludeより前に)。デフォルトでは 2 になります。 --a_DbgLv=0 : リリース用のモードで、ライブラリ側でのエラーチェックは一切やらなくなります。ただし、プログラムのバグではなく環境によっておこるエラー(メモリ確保の失敗、ファイルオープン時にファイルが見つからないなど)はチェックします。 --a_DbgLv=1 : エラーチェックはa_DbgLv=0と同等にまで省略されていますが、構造体の中にはエラーチェックのための情報を入れるスペースを確保します。これは「デバッグレベル2だと生じないのに、デバッグレベル0だと発生するバグ」なんて言う厄介な問題が出たときに、問題の切り分けを支援するために用意されているもので、普通はこのレベルを使うことはありません。 --a_DbgLv=2 : 処理速度があまり落ちないようなエラーチェックはすべてやります(デフォルト)。 --a_DbgLv=3 : レベル2とレベル5の間です。 --a_DbgLv=4 : レベル2とレベル5の間です。 --a_DbgLv=5 : どれだけ遅くなってもいいので、今実装されているエラーチェックは全部有効にします。 -acl4v2では、このデバッグレベルの考え方を重視しています。理由はこうです。・・・デバッグレベルがあるので、どれだけエラーチェックをしてもリリース時に実行速度の低下はありません。「こんなチェックをしたら確かに強力だけど、遅くて使い物にならない」みたいなチェックアルゴリズムがあった場合に、ためらうことなくエラーチェックのコードをいれることができます。必要なことはエラーチェックのための記述をためらうことではなく、どのデバッグレベル以上で有効になるのか正しく設定することだけです。 -私自身は「まあこんなにチェックしても、自分がこのチェックで助かることはめったにないだろう」なんてうぬぼれていたのですが、これを書いて数週間後には、チェックがあるとわかっているので「バグ探しはライブラリにやってもらえばいいやー」と適当に書くようになって、結果的にのびのびとプログラムを作れるようになりました。気分はいいです。おかげで新規にコードを書くときは1日に1回くらいはこのエラーチェック機能に助けられています。 -とまあ概念的なことばかり説明していてもよくわからないと思うので、後で実際のコードを使いながら説明します。 ** (2) a_malloc(), a_free(), a_realloc() -これからエラーチェック機能付きのmalloc/free/reallocを作ります。要件はこんな感じです。 --[1]a_DbgLv=0にした場合、標準関数のmalloc/free/reallocにメモリ確保失敗チェックを付けた程度のものになるようにします(a_NoUse_OutOfMemoryCheck!=0のときはメモリ確保失敗チェックすらしません)。 --[2]a_DbgLv=2以上の場合は以下の機能を持ちます: --[3]メモリを確保した場合に、その中身を 0x87 で塗りつぶします。これは「ここは未初期化です」というマークのつもりです。ゼロクリアとかだと、未初期化のまま進んでも何もトラブルが起きなくて気づけないことがよくあるのですが、 0x87 で埋め尽くしているところを未初期参照して値を使おうとすると、たいてい派手にコケてくれるので気づきやすいです。a_realloc()で容量を広げた場合も、広げた部分は 0x87 で埋めます。 0x87 は他の用途ではめったに出てこない値(かつコケやすい値)なのでこれを採用しました。 --[4]メモリを開放する場合にも、あえて中身を0x87で塗りつぶしてからfree()します。これはたまにミスって use-after-free をやってしまった場合に、元のデータが無くなっていると気付きやすいためです。 --[5]メモリ確保の際には、前後にマージンを付けて確保します。そのマージン部分は 0xa5 で埋めておきます。これは「カナリア」と呼ばれるテクニックで、例えばプログラムがバッファオーバーランなどを起こすと、このカナリアを壊してしまいます。ライブラリはメモリの解放の際にはカナリアを確認しますが、もし壊されていたらさらにその外側にあるヒープメモリチェーンも壊れている可能性が高くて続行不能なので、エラー終了します。このバグを見落とすと追跡困難なバグになりやすいので、これは結構有用です。 --[6]確保したメモリの一覧がいつでも表示できるように、情報を貯めます。これで開放し忘れがないか確認できます。この情報は確保したメモリブロックのそばには置きません。なぜならカナリアと一緒に壊される可能性があるからです。もっと別の安全そうな場所に置くことにします。 ** (3) a_ というマクロ -acl4v2 ライブラリでは a_ という引数がちょくちょく現れます。これは「 __FILE__, __LINE__, 」にdefineされていて、a_malloc()などがソースコードの何行目から呼び出されたのかを伝えるための仕組みです。できるだけ目立たないようにするためにこうなっています。 -ちなみに a_DbgLv=0 のときは、 a_ は「」になります。つまり消えます。だってリリースモードなのに余計な引数なんて渡して処理速度が落ちたら嫌ですよね?だからきれいに消えるのです。 -なぜ a_malloc() や a_free() で a_ を渡す必要があるのか説明します。まず a_free() のほうは簡単です。 a_free() でカナリア破壊検出などのエラーがあったときに、それはどの a_free() を呼んだときに気づいたのかわからないと、すごく不便だからです。ああ、〇〇行目の a_free() でこけたかーってわかればたいていすぐに直せます。 -次に a_malloc() のほうです。 a_malloc() はメモリ不足以外では失敗しません。だからエラー個所を特定する必要なんて、ほぼないのです。しかもメモリ不足って、別にどこの行で起きたとか言われてもあまり参考になりません。メモリが尽きたタイミングでどこでも起きますし、起きてしまったら終了する以外にできることはありません(ごくまれにメモリが尽きたら重要度の低いデータを捨てて続行し、重要なデータをセーブさせる、というプログラムを作る人もいますが)。・・・まあ例外として、サイズ指定を間違えたバグなのであれば、エラー行が分かることが役に立ちますが。 -じゃあなぜ a_ をとるのかと言えば、それは確保したメモリブロックに名前を付けるためです。〇〇行目の a_malloc() で確保したサイズ〇〇バイトのメモリってわかると、ああこれのことかーって気づけるんです。そうでないと、メモリアドレスとか言われても、いや、それってどのメモリのことですか?ってなるんです。 ** (4) acl4v2_001.c #if (!defined(a_DbgLv)) #define a_DbgLv 2 #endif #if (!defined(a_NoUse_OutOfMemoryCheck)) #define a_NoUse_OutOfMemoryCheck 0 #endif #if (a_Version > 0) #define DbgLv a_DbgLv #define errExit a_errExit #define DM a_DM #define malloc_ a_malloc #define free_ a_free #define realloc_ a_realloc #define free1 a_free1 #define realloc1 a_realloc1 #define malloc_chkAll a_malloc_chkAll #endif #if (a_DbgLv >= 1) #define a_DbgLv1(x) x #else #define a_DbgLv1(x) #endif #if (a_DbgLv >= 2) #define a_DbgLv2(x) x #define a_ __FILE__, __LINE__, #define a__ __FILE__, __LINE__ #define a_def const char *a_fil, int a_lin, #define a_def_ const char *a_fil, int a_lin #define a_thr a_fil, a_lin, #define a_thr_ a_fil, a_lin #else #define a_DbgLv2(x) #define a_ #define a__ #define a_def #define a_def_ #define a_thr #define a_thr_ #endif //// a_static void a_errExit0(const char *f, ...) { va_list ap; va_start(ap, f); vfprintf(stderr, f, ap); va_end(ap); fprintf(stderr, "\n"); exit(1); } #if (!defined(a_errExit)) #define a_errExit a_errExit0 #endif #define a_DM a_DbgLv2( fprintf(stderr, "%s(%d): DebugMessage\n", __FILE__, __LINE__); ) #define a_malloc_chkAll(flg) a_DbgLv2(a_MallocMgr_chkAll(a_mallocMgr, flg)) //// a_class(a_MallocSub_Node) { intptr_t prv, nxt; }; a_class(a_MallocSub) { void *(*rlc)(void *, size_t); char *p; intptr_t sz, n; }; #define HedUse 0 #define HedFre 1 #define a_MallocSub_Nod(w, i) ((a_MallocSub_Node *) (w->p + i * w->sz)) #define Nod(w, i) a_MallocSub_Nod(w, i) a_static void a_MallocSub_iniHed(a_MallocSub *w, intptr_t i) { Nod(w, i)->prv = i; Nod(w, i)->nxt = i; } a_static void a_MallocSub_add(a_MallocSub *w, intptr_t prv, intptr_t i, intptr_t nxt) { Nod(w, i )->prv = prv; Nod(w, i )->nxt = nxt; Nod(w, nxt)->prv = i; Nod(w, prv)->nxt = i; } a_static void a_MallocSub_rmv(a_MallocSub *w, intptr_t i) { intptr_t prv = Nod(w, i)->prv, nxt = Nod(w, i)->nxt; Nod(w, prv)->nxt = nxt; Nod(w, nxt)->prv = prv; } a_static void a_MallocSub_fre1(a_MallocSub *w, intptr_t i0, intptr_t i1) { intptr_t i; memset(w->p + i0 * w->sz, 0, (i1 - i0 + 1) * w->sz); // ゴミデータが残っていると面倒なのでゼロクリア. for (i = i1; i >= i0; i--) a_MallocSub_add(w, HedFre, i, Nod(w, HedFre)->nxt); } a_static intptr_t a_MallocSub_alc(a_MallocSub *w) { if (Nod(w, HedFre)->nxt == HedFre) { // 未使用Nodeがないので自動拡張. w->p = w->rlc(w->p, w->sz * w->n * 2); if (w->p == NULL) a_errExit("a_MallocSub: out of memory"); a_MallocSub_fre1(w, w->n, w->n * 2 - 1); w->n *= 2; } intptr_t i = Nod(w, HedFre)->nxt; a_MallocSub_rmv(w, i); return i; } //// a_class(a_MallocInf) { a_MallocSub_Node nod[1]; intptr_t sz, lin, no; const char *fil; void *p; }; a_class(a_MallocMgr) { a_MallocSub sub[1]; unsigned char *canary; intptr_t szCanary, no; unsigned char unuseMark; }; a_static a_MallocMgr a_mallocMgr[1]; a_static void a_MallocMgr_ini(a_MallocMgr *w, intptr_t n) { w->sub->rlc = realloc; w->sub->p = malloc(n * sizeof (a_MallocInf)); w->sub->sz = sizeof (a_MallocInf); w->sub->n = n; a_MallocSub_iniHed(w->sub, HedUse); a_MallocSub_iniHed(w->sub, HedFre); a_MallocSub_fre1(w->sub, 2, n - 1); #if (a_DbgLv >= 4) w->szCanary = 1024 * sizeof (intptr_t); #elif (a_DbgLv == 3) w->szCanary = 256 * sizeof (intptr_t); #else w->szCanary = 16 * sizeof (intptr_t); #endif w->canary = malloc(w->szCanary); memset(w->canary, 0xa5, w->szCanary); w->unuseMark = 0x87; w->no = 0; } a_static void *a_MallocMgr_alc(a_MallocMgr *w, a_def intptr_t sz) { #if (a_DbgLv <= 1) (void) w; void *p = malloc(sz); if (p == NULL) a_errExit("a_malloc: out of memory: sz=%d", sz); return p; #else a_MallocSub *v = w->sub; if (w->sub->rlc == NULL) a_MallocMgr_ini(w, 16); if (sz < 0) a_errExit("%s(%d): a_malloc: sz=%d", a_fil, a_lin, (int) sz); char *p = malloc(sizeof (intptr_t) + w->szCanary * 2 + sz); if (p == NULL) a_errExit("%s(%d): a_malloc: out of memory: sz=%d", a_fil, a_lin, (int) sz); char *p1 = p + sizeof (intptr_t); memcpy(p1, w->canary, w->szCanary); char *p2 = p1 + w->szCanary; memset(p2, w->unuseMark, sz); memcpy(p2 + sz, w->canary, w->szCanary); intptr_t i = a_MallocSub_alc(v); *(intptr_t *) p = i; a_MallocSub_add(v, Nod(v, HedUse)->prv, i, HedUse); a_MallocInf *inf = (a_MallocInf *) Nod(v, i); inf->sz = sz; inf->lin = a_lin; inf->fil = a_fil; inf->p = p2; inf->no = w->no++; return p2; #endif } a_static int a_MallocMgr_chk(a_MallocMgr *w, intptr_t i, void *p, intptr_t sz) { a_MallocSub *v = w->sub; a_MallocInf *inf; intptr_t j; if (p == NULL) { if (i < 2 || v->n <= i) a_errExit("a_MallocMgr_chk: err: p=NULL, i=%d", i); inf = (a_MallocInf *) Nod(v, i); p = inf->p; } #if (a_DbgLv >= 4) j = Nod(v, HedUse)->prv; // 新しいものから(=最後尾から)順番に探す. 多分そのほうが速く見つかる. for (;;) { if (j == HedUse) return 1; // 不正ポインタ検出. inf = (a_MallocInf *) Nod(v, j); if (inf->p == p) break; j = inf->nod->prv; } if (j != *(intptr_t *) ((char *) p - w->szCanary - sizeof (intptr_t))) return 2; // malloc情報indexが一致しない#1. #endif if (memcmp((char *) p - w->szCanary, w->canary, w->szCanary) != 0) return 3; // カナリア(ヘッダー)の破壊を検出. j = *(intptr_t *) ((char *) p - w->szCanary - sizeof (intptr_t)); if (i < 2) i = j; if (i < 2 || v->n <= i) return 4; // malloc情報indexが異常値. if (i != j) return 5; // malloc情報indexが一致しない#2. inf = (a_MallocInf *) Nod(v, i); if (p != inf->p) return 6; // ポインタが一致しない(==ID値が壊されている). if (sz < 0) sz = inf->sz; if (sz != inf->sz) return 7; // サイズが一致しない. if (memcmp((char *) p + sz, w->canary, w->szCanary) != 0) return 8; // カナリア(フッター)の破壊を検出. return 0; } a_static intptr_t a_MallocMgr_chkErr(a_MallocMgr *w, a_def intptr_t i, void *p, intptr_t sz, const char *f) { #if (a_DbgLv >= 2) int chk = a_MallocMgr_chk(w, i, p, sz); if (1 <= chk && chk <= 6) a_errExit("%s(%d): %s: bad signature(i=%d, p=0x%x, sz=%d, chk=%d)", a_fil, a_lin, f, (int) i, (int) (intptr_t) p, (int) sz, chk); char *p0 = (char *) p - w->szCanary - sizeof (intptr_t); i = *(intptr_t *) p0; a_MallocInf *inf = (a_MallocInf *) Nod(w->sub, i); if (chk >= 7) { a_errExit("%s(%d): %s: bad signature(i=%d, p=0x%x, sz=%d, no=%d, chk=%d, [%s(%d): sz=%d])", a_fil, a_lin, f, (int) i, (int) (intptr_t) p, (int) sz, (int) inf->no, chk, inf->fil, (int) inf->lin, (int) inf->sz); } #endif return i; } a_static void a_MallocMgr_fre(a_MallocMgr *w, a_def void *p, intptr_t sz) { #if (a_DbgLv <= 1) (void) w; (void) sz; free(p); #else if (p == NULL) return; intptr_t i = a_MallocMgr_chkErr(w, a_thr 0, p, sz, "a_free"); a_MallocInf *inf = (a_MallocInf *) Nod(w->sub, i); char *p0 = (char *) p - w->szCanary - sizeof (intptr_t); memset(p0, w->unuseMark, sizeof (intptr_t) + w->szCanary * 2 + inf->sz); free(p0); a_MallocSub_rmv(w->sub, i); inf->p = NULL; a_MallocSub_add(w->sub, HedFre, i, Nod(w->sub, HedFre)->nxt); #endif } a_static void *a_MallocMgr_rlc(a_MallocMgr *w, a_def void *p, intptr_t sz, intptr_t sz0) { #if (a_DbgLv <= 1) (void) w; (void) sz0; p = realloc(p, sz); if (p == NULL) a_errExit("a_realloc: out of memory: sz=%d", sz); return p; #else if (w->sub->rlc == NULL) a_MallocMgr_ini(w, 16); if (sz < 0) a_errExit("%s(%d): a_realloc: p=0x%x, sz=%d", a_fil, a_lin, (int) (intptr_t) p, (int) sz); if (p != NULL) { intptr_t i = a_MallocMgr_chkErr(w, a_thr 0, p, sz0, "a_realloc"); a_MallocInf *inf = (a_MallocInf *) Nod(w->sub, i); sz0 = inf->sz; if (sz0 == sz) return p; char *q = malloc(sizeof (intptr_t) + w->szCanary * 2 + sz); // サイズが縮小する場合でもアドレスは変わる仕様. if (q == NULL) a_errExit("%s(%d): a_realloc: out of memory: sz=%d", a_fil, a_lin, (int) sz); char *q1 = q + sizeof (intptr_t); memcpy(q1, w->canary, w->szCanary); char *q2 = q1 + w->szCanary; if (sz > sz0) { memcpy(q2, p, sz0); memset(q2 + sz0, w->unuseMark, sz - sz0); } else memcpy(q2, p, sz); memcpy(q2 + sz, w->canary, w->szCanary); *(intptr_t *) q = i; inf->sz = sz; inf->p = q2; char *p0 = (char *) p - w->szCanary - sizeof (intptr_t); memset(p0, w->unuseMark, sizeof (intptr_t) + w->szCanary * 2 + sz0); free(p0); return q2; } return a_MallocMgr_alc(w, a_thr sz); #endif } #if (a_DbgLv <= 1 && a_NoUse_OutOfMemoryCheck != 0) #define a_malloc malloc #define a_free free #define a_realloc realloc #define a_free1(p, sz) free(p) #define a_realloc1(p, sz, sz0) realloc(p, sz) #endif #if (a_DbgLv <= 1 && a_NoUse_OutOfMemoryCheck == 0) #define a_malloc(sz) a_MallocMgr_alc(NULL, sz) #define a_free(p) a_MallocMgr_fre(NULL, p, 0) #define a_realloc(p, sz) a_MallocMgr_rlc(NULL, p, sz, 0) #define a_free1(p, sz) a_MallocMgr_fre(NULL, p, 0) #define a_realloc1(p, sz, sz0) a_MallocMgr_rlc(NULL, p, sz, 0) #endif #if (a_DbgLv >= 2) #define a_malloc(sz) a_MallocMgr_alc(a_mallocMgr, sz) #define a_free(p) a_MallocMgr_fre(a_mallocMgr, p, -1) #define a_realloc(p, sz) a_MallocMgr_rlc(a_mallocMgr, p, sz, -1) #define a_free1(p, sz) a_MallocMgr_fre(a_mallocMgr, p, sz) #define a_realloc1(p, sz, sz0) a_MallocMgr_rlc(a_mallocMgr, p, sz, sz0) #endif a_static void a_MallocMgr_chkAll(a_MallocMgr *w, a_def int flg) { #if (a_DbgLv >= 2) fprintf(stderr, "%s(%d): a_MallocMgr_chkAll: a_DbgLv=%d\n", a_fil, a_lin, a_DbgLv); a_MallocSub *v = w->sub; intptr_t i = Nod(v, HedUse)->nxt, n = 0, total = 0, nChk = 0; for (;;) { if (i == HedUse) break; a_MallocInf *inf = (a_MallocInf *) Nod(v, i); int chk = a_MallocMgr_chk(w, i, NULL, -1); if ((flg & 1) != 0 || chk != 0) { fprintf(stderr, " #%04d: chk=%d [i=%04d p=0x%08x sz=%04d no=%04d : %s(%d)]\n", (int) n, chk, (int) i, (int) (intptr_t) inf->p, (int) inf->sz, (int) inf->no, inf->fil, (int) inf->lin); if (chk != 0) nChk++; } total += inf->sz; i = Nod(v, i)->nxt; n++; } fprintf(stderr, " n=%d, total=%d, nChk=%d\n", (int) n, (int) total, (int) nChk); if (nChk > 0) exit(1); #endif } #undef HedUse #undef HedFre #undef Nod ** (5) 解説 -''[5-1]'' まず最初に確認してもらいたいのは、最後のほうにある読み替え用の define です。 a_DbgLv<=1 で、かつ a_NoUse_OutOfMemoryCheck!=0 であれば、以下のようになります。 #define a_malloc malloc #define a_free free #define a_realloc realloc #define a_free1(p, sz) free(p) #define a_realloc1(p, sz, sz0) realloc(p, sz) -つまりここに書かれた関数はほぼ全部使わないことになって、標準関数をそのまま使うという、まったくオーバーヘッドのない形になります。 -これに対して、 a_DbgLv>=2 であれば、以下のような読み替えになります。これでまずどれがどの役割を持つのか把握しておくと理解しやすいはずです。 #define a_malloc(sz) a_MallocMgr_alc(a_mallocMgr, sz) #define a_free(p) a_MallocMgr_fre(a_mallocMgr, p, -1) #define a_realloc(p, sz) a_MallocMgr_rlc(a_mallocMgr, p, sz, -1) #define a_free1(p, sz) a_MallocMgr_fre(a_mallocMgr, p, sz) #define a_realloc1(p, sz, sz0) a_MallocMgr_rlc(a_mallocMgr, p, sz, sz0) -ついでなので、ここで a_free1() や a_realloc1() について説明しておこうと思います。これらは標準関数にはない関数ですが、簡単に言ってしまえば free() するときにサイズ指定をするだけの関数です( realloc でも旧サイズを申告します)。このとき、好き勝手なサイズを指定できるわけではなく、 malloc() したときのサイズを正しく申告する必要があります。 -これは malloc() を自作してみるとわかりやすいのですが、もし free するときにアプリ側が確実にサイズを教えてくれるのだとしたら、 malloc 側でサイズを覚えておく必要が無くなって処理を簡略化できるのです(処理の簡略化だけではなく、メモリの利用効率も改善します)。それで、自作OSではそういう仕様の malloc() を書いたことがあります。アプリが free 時にサイズを申告するのは面倒だと思うかもしれませんが、アプリはほとんどの場合で free するとき解放領域のサイズを知っているので、やってみると大した手間ではありません。これはこれで十分にアリな仕様だと感じました(もちろん簡単にはわからない場合もあります。その場合は malloc の時に指定したサイズを記録する処理をアプリ側が書くことになります)。 -それで、普段から free1() や realloc1() でアプリを書いておけば、どちらの環境でも動くアプリが簡単に書けるので、私はこれらの関数も準備しておくのが好きなのです。 -''[5-2]'' それでじゃあ、まあ最初から眺めていくと、 a_errExit() までは今までの説明でなんとなくわかると思います。 a_errExit() では、直接この関数を書かずに、 a_errExit0() を作って、 a_errExit() を a_errExit0() に読み替えさせるような回りくどいことをしています。なぜこんなことをしているのか説明したいです。 -この a_errExit() 関数は、基本的にエラーメッセージを表示して exit(1) するだけの取るに足らない関数です。こんな関数をわざわざ作る必要があるのか?毎回 fprintf() して exit(1) すればいいだけじゃないかと言われると、実はその通りです。でもこういう些細な関数こそ、ライブラリ自作では大活躍します。 -逆に、なんか高度なことをいろいろやってくれる大規模な関数は、活躍できる機会があまり多くありません。つまり小さな基本部品から大きな部品をいきなり作るのではなく、たくさんの階層に分けて少しずつ作っていくべきなのです。・・・そういうことが分かってから標準関数をみると、 strlen() とか strcpy() とか、とても小規模な関数が多いと気付かされます。こういう作り方がうまいのです。・・・ライブラリ自作で大事なことは、自作したライブラリ関数をできるだけたくさん使うことです。使わないならライブラリ自作駆動開発の前提が崩れてしまいます。些細な関数のほうが使う機会が増えるのなら、些細な関数こそ作られるべきなのです。 -ということで a_errExit() は作られるべき関数の代表みたいなものなのですが、しかし常にこのエラー処理でいいのかという迷いがあります。例えばグラフィカルなアプリだったら、標準エラー出力ではなく専用のメッセージボックスに表示したいかもしれません。そういう場合、 a_errExit() を自前のエラー処理関数に差し替えたくなります。だから必要になったら簡単に差し替えばできるように、読み替え方式になっているのです。 -''[5-3]'' 次は DM関数です。これは DebugMessage の略です。・・・これは、ええと、プログラムをデバッグしているときに、「あれ?ここは通ったかな?」という確認をするために printf("hello\n"); とか printf("hello2\n"); とかやることがありませんか?(もしかして今どきのプログラマこんなことはしないのかな?)。この hello とかを考えるのが面倒なので、そういうときはただ DM と書くのです。かっこもセミコロンもいりませんし、 a_DbgLv<=1 のときは自動で消えてくれます。「hello」のようなメッセージ内容は指定できないのですが、代わりに行番号が出るので、むしろめんどくさいことを考えなくてよくて重宝します。 -''[5-4]'' 次は a_MallocSub クラスです。・・・まずこの名前はあまりよくありません。 malloc のために作ったクラスではありますが、それ以外の用途に使えないということはなく、だからもっといい名前を付けるべきでした。それは分かっていたのですが、いいクラス名を思いつくことができなくて、迷っている時間がもったいないのでこの名前になりました。 -この辺りからポインタを多用していて、なんかよくわからないなーってなる人もいると思います。そういう時は''無理して理解しようとはしないで、何をしてくれる関数なのかということだけ理解すれば十分だと思います。''だってそもそも標準関数だって、使い方を理解できればそれで十分に使えますよね。本来、ライブラリは実装の細部を理解する必要はないのです。 -このクラスは、「双方向循環リスト」というデータ構造を使って malloc 情報を登録順に並べて管理しています。 --双方向循環リストはここに説明があります: https://ja.wikipedia.org/wiki/%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88 -イメージとしては a_MallocInf の配列があって、それぞれの inf には malloc 情報のほかに、「前(prv)」と「次(nxt)」という情報があって、これを使って、inf[5]の次はinf[8]、みたいに登録順序をたどっていくことができます。prvがあるので最後尾から始まって逆順にたどっていくこともできます。 -なぜ双方向循環リストを使うのか?・・・単純に考えたら、inf[0],inf[1],inf[2]の順番に並べておけばよさそうに思えます。しかし1000個くらい登録された状態で、inf[4]のメモリブロックが free されたら、inf[5]~inf[999]を全部ずらさなければいけません。このずらし処理には memmove() を使いますが、転送量が多いと結構時間がかかります。ずらし処理をしたくなかったので、prvとnxtのある双方向循環リストにしたのです。これならずらす必要がありません。prvとnxtをつなぎ変えるだけで inf[4] を切り離すことができます。この切り離し処理は a_MallocSub_rmv() がやっています。 -一方、 malloc したときには inf[i] を接続しなければいけません。接続処理は a_MallocSub_add() がやっています。 -実は a_MallocSub クラスでは、2つの双方向循環リストを持っています。一つは a_MallocInf を登録順に並べたものですが、もう一つは何の情報も入っていない空(から)の inf[] を集めて並べて置いたものです。配列内のすべての inf[] は必ずこのどちらかの双方向循環リストに所属することになっています。・・・なぜ空の inf[] をつないでおく必要があるのかというと、それがないと malloc して新規登録しなければいけないときに、未使用の inf[] を探すのが大変になるからです。双方向循環リストから探す方法なら一瞬でできます。 -実は未使用管理のほうは、双方向にする必要はなくて片方向でも十分に目的を果たせるとわかっていたのですが、わざわざそのために片方向ようの関数を書くのがめんどくさいということになり、そのまま未使用管理でも双方向で管理するようにしています。 -a_MallocSub_iniHed(w, i): inf[i]を双方向循環リストの番兵として初期化します。 -a_MallocSub_add(w, prv, i, nxt): inf[i]を双方向循環リストに加えます。iから見て、prvとnxtに当たる配列番号も指定します。 -a_MallocSub_rmv(w, i): inf[i]を双方向循環リストから切り離します。 -a_MallocSub_fre1(w, i0, i1): inf[i0]からinf[i1]を未使用リストに追加します。初期化のときに使います。 -a_MallocSub_alc(w): 未使用リストから1つとってきます。もし未使用が一つもないときは realloc() で配列を拡張して未使用を作って、それから未使用を1つ返します。 -''[5-5]'' 次は a_MallocMgr クラスです。 malloc のマネジャーです。 -これはメモリを確保するときに、[inf配列の番号][カナリア(ヘッダー)][szバイト][カナリア(フッター)]になるように工夫した malloc です。アプリには sz バイトの部分を渡します。アプリはバグによって与えられた範囲をはみ出してしまう可能性があるので、前後をカナリアで挟んで保護している感じです。ちょっとのはみだしで済めば、他に悪影響を与えることはないわけです。 -a_MallocMgr_ini(w, n): a_mallocMgr を初期化します。a_DbgLvの値に応じてカナリアの大きさを変えています。a_DbgLv>=4の場合、カナリアは4KB以上になります。おおきい!! -a_MallocMgr_alc(w, sz): a_malloc() の処理をします。 -a_MallocMgr_chk(w, i, p, sz): iもしくはpで指定されたメモリブロックが壊れていないかテストします。壊れていなければ0を返し、壊れていたら1以上の値を返します。 -a_MallocMgr_chkErr(w, i, p, sz, f): 指定されたメモリブロックを chk() で調査して、もし壊れていればエラーメッセージを出して強制終了します。 -a_MallocMgr_fre(w, p, sz): a_free() の処理をします。 -a_MallocMgr_rlc(w, p, sz, sz0): a_realloc() の処理をします。 -a_MallocMgr_chkAll(w, flg): a_malloc() で確保済みのメモリについて、すべて chk します。flg=1の場合は正常なものも異常なものも報告します。flg=0の場合は、異常なもののみ報告します。 -このプログラムでは intptr_t を int にキャストしてから printf しています。こうすることで64bit環境などでは、上位bitが落ちて、下位bitしか見えなくなるかもしれません。そこをちゃんとしようとすると結構大変なので、今回は妥協しました。まあデバッグ用途であれば、下位bitのみの表示でも困ることはあまりないのではないかと思います。 ** (6) サンプルコード sample001a.c -以下のプログラムで試してみました。 #include <acl4v2.c> int main(int argc, const char **argv) { char *a = malloc_(a_ 10); char *b = malloc_(a_ 20); char *c = malloc_(a_ 30); free_(a_ b); strcpy(a, "0123456789"); // 10文字だから大丈夫・・・と思いきや、これをやると最後の'\0'がはみ出してしまいます. malloc_chkAll(a_ 1); // プログラム終了前にこれを書くくせをつけておくと、メモリリークがどんどん見つかります. return 0; } -実行結果は以下の通りです。 sample001a.c(10): a_MallocMgr_chkAll: a_DbgLv=2 #0000: chk=8 [i=0002 p=0xfd084a98 sz=0010 no=0000 : sample001a.c(5)] #0001: chk=0 [i=0004 p=0xfd084ce8 sz=0030 no=0002 : sample001a.c(7)] n=2, total=40, nChk=1 -mallocされたがまだfreeしてないメモリは2つあり、それがaとcのことだとすぐにわかります(行番号が分かるので)。 -またaのほうはカナリア(フッター)が壊れているということもわかります。 -sample001a.c くらいの記述量で、ここまで情報が取れるっていうのは、結構いい感じだと私は思います。これなら今後は acl4v2 の中で暮らしてもいいかなーと。 -これはOSやCPUに依存するデバッガではないので、どんな環境でも同じように使えるというところも気に入っています。 ** (7) サンプルコード sample001b.c -以下のプログラムで試してみました。 // #define a_DbgLv 4 #include <acl4v2.c> int main(int argc, const char **argv) { char *a = malloc_(a_ 10); char *b = malloc_(a_ 20); char *c = malloc_(a_ 30); free_(a_ a); free_(a_ b); free_(a_ a); // ここで間違えて、二重freeをしている. malloc_chkAll(a_ 1); // プログラム終了前にこれを書くくせをつけておくと、メモリリークがどんどん見つかります. return 0; } -実行結果は以下の通りです。 sample001b.c(11): a_free: bad signature(i=0, p=0xf3b5aea8, sz=-1, chk=3) -二重freeのところでエラー終了したことが分かります。 chk=3 なのでカナリア(ヘッダー)が壊れているという扱いになっていることが分かります。これは一度目の正しいfreeで、カナリアをわざと壊しているからです。カナリアをあえて壊すのは、こうやって二重freeをしようとしたときに検出できるようにするためです。 -ちなみに a_DbgLv=4 でビルドすると、エラー終了の時の理由が変わって chk=1 になります。これはもはやカナリアを確認するまでもなく、指定されたポインタが inf[] の使用中リストの中に入ってないから、カナリア部分をメモリアクセスしなくてもおかしいってわかったよ、というエラーです。 ** (8) あとがき -普通 mallocやfreeの自作をするとなったら、自作mallocはこんなに高速なんだぜ!って性能自慢をすると思います。私だってそういう時期がありましたよ。・・・でも acl4v2 ではそんな話は全く出てこなくて、最初から最後まで、デバッグ支援機能の話ばかりです。・・・でも考えてみてください。開発効率(開発速度)を上げていこうと思ったら、まず最初にやるべきはデバッグ支援だと私は思うのです。デバッグのために費やされる時間に何の意味があるでしょう。そんなのはできるだけ少なくするべきなんです。私は、そしてこれを読んでいる皆さんも、きっと将来はスーパーなプログラマになります(予定)。スーパーなプログラマは、自分にしかできない開発をどんどんやってみんなを引っ張っていかなければいけません!(笑)。 malloc の高速化なんて、できそうな人はまあまあいそうです。だからその人たちに任せればいいのです。私たちは、それ以外の場所をさっさと作りましょう。速くなったらどんなによくなるかなんて、別に私たちがやらなくてもどんなものなのか想像できます。でも新しい機能は、私たちが作って見せないとわからないじゃないですか。そんな時、デバッグに足を引っ張られていたらもったいないのです! -ちなみにスーパーなプログラマがいい機能をどんどん作れる最大の秘訣は、思いついたものを次々と作ることです。結局私たちだって、作って確認しないとわからないんです。だからどんどん作りましょう。失敗作もたくさんできますが、いろいろ作っていいものだけを世に出すので、「なんかこいつスゲーな!」になるんです。 * こめんと欄 #comment
タイムスタンプを変更しない
* 川合のプログラミング言語自作のためのテキスト第四版#001 -(by [[K]], 2026.05.28) --目次はこちらです。→[[a26_txt03]] ** (1) デバッグレベル -acl4v2 には「デバッグレベル」という概念があります。 -このデバッグレベルは、コンパイラの最適化レベルのことだろうと誤解されることがあるのですが、それは違います。・・・紛らわしいのは「デバッグ時は最適化レベルを最低にしましょう」という正しいアドバイスがあって、それでデバッグと最適化レベルが結びついているせいだと思います。 -コンパイラの最適化は、生成する機械語の質を上げるためなら、プログラムに書かれた通りには処理しないで、あえて順序を変えたり共通化部分をまとめたりします(でも実行結果は変わりません)。こういうことをされると、変数〇〇に3が代入されるところからトレースしようとか思っても、デバッグがうまくいかないのです。コンパイラはその変数の更新タイミングを保持しなければいけないとは気づけないので、順序入れ替えなどをやってしまっているせいです。・・・だからデバッグ時は最適化をできるだけしないようにするわけです。・・・なお、printfでデバッグをする場合は、printf結果が変わってしまうと実行結果を変えたことになってしまうので、最適化レベルがmaxであっても支障はありません(最適化レベルが高いとコンパイルに時間がかかるという問題はありますが)。 -acl4v2 のデバッグレベルは最適化とは関係なくて、「このプログラムはまだバグがあるかもしれないから、とにかく用心深くチェックしながら実行してね」という意味です。つまりデバッグレベルが5のときでも、最適化レベルを最高にして構いませんし、デバッグレベルが0のときに最適化レベルを最低にしてもかまいません。速いか遅いかというよりも、バグチェックをしながら実行するかどうかの違いです(まあチェックをすれば基本的には少し遅くなりますが)。 -デバッグレベルによるチェック強化は、すべて実行時チェックによるものなので、コンパイル時に追加でエラーが見つかるということはありません(残念ながら)。 -デバッグレベルは a_DbgLv をdefineして指定します(ライブラリのincludeより前に)。デフォルトでは 2 になります。 --a_DbgLv=0 : リリース用のモードで、ライブラリ側でのエラーチェックは一切やらなくなります。ただし、プログラムのバグではなく環境によっておこるエラー(メモリ確保の失敗、ファイルオープン時にファイルが見つからないなど)はチェックします。 --a_DbgLv=1 : エラーチェックはa_DbgLv=0と同等にまで省略されていますが、構造体の中にはエラーチェックのための情報を入れるスペースを確保します。これは「デバッグレベル2だと生じないのに、デバッグレベル0だと発生するバグ」なんて言う厄介な問題が出たときに、問題の切り分けを支援するために用意されているもので、普通はこのレベルを使うことはありません。 --a_DbgLv=2 : 処理速度があまり落ちないようなエラーチェックはすべてやります(デフォルト)。 --a_DbgLv=3 : レベル2とレベル5の間です。 --a_DbgLv=4 : レベル2とレベル5の間です。 --a_DbgLv=5 : どれだけ遅くなってもいいので、今実装されているエラーチェックは全部有効にします。 -acl4v2では、このデバッグレベルの考え方を重視しています。理由はこうです。・・・デバッグレベルがあるので、どれだけエラーチェックをしてもリリース時に実行速度の低下はありません。「こんなチェックをしたら確かに強力だけど、遅くて使い物にならない」みたいなチェックアルゴリズムがあった場合に、ためらうことなくエラーチェックのコードをいれることができます。必要なことはエラーチェックのための記述をためらうことではなく、どのデバッグレベル以上で有効になるのか正しく設定することだけです。 -私自身は「まあこんなにチェックしても、自分がこのチェックで助かることはめったにないだろう」なんてうぬぼれていたのですが、これを書いて数週間後には、チェックがあるとわかっているので「バグ探しはライブラリにやってもらえばいいやー」と適当に書くようになって、結果的にのびのびとプログラムを作れるようになりました。気分はいいです。おかげで新規にコードを書くときは1日に1回くらいはこのエラーチェック機能に助けられています。 -とまあ概念的なことばかり説明していてもよくわからないと思うので、後で実際のコードを使いながら説明します。 ** (2) a_malloc(), a_free(), a_realloc() -これからエラーチェック機能付きのmalloc/free/reallocを作ります。要件はこんな感じです。 --[1]a_DbgLv=0にした場合、標準関数のmalloc/free/reallocにメモリ確保失敗チェックを付けた程度のものになるようにします(a_NoUse_OutOfMemoryCheck!=0のときはメモリ確保失敗チェックすらしません)。 --[2]a_DbgLv=2以上の場合は以下の機能を持ちます: --[3]メモリを確保した場合に、その中身を 0x87 で塗りつぶします。これは「ここは未初期化です」というマークのつもりです。ゼロクリアとかだと、未初期化のまま進んでも何もトラブルが起きなくて気づけないことがよくあるのですが、 0x87 で埋め尽くしているところを未初期参照して値を使おうとすると、たいてい派手にコケてくれるので気づきやすいです。a_realloc()で容量を広げた場合も、広げた部分は 0x87 で埋めます。 0x87 は他の用途ではめったに出てこない値(かつコケやすい値)なのでこれを採用しました。 --[4]メモリを開放する場合にも、あえて中身を0x87で塗りつぶしてからfree()します。これはたまにミスって use-after-free をやってしまった場合に、元のデータが無くなっていると気付きやすいためです。 --[5]メモリ確保の際には、前後にマージンを付けて確保します。そのマージン部分は 0xa5 で埋めておきます。これは「カナリア」と呼ばれるテクニックで、例えばプログラムがバッファオーバーランなどを起こすと、このカナリアを壊してしまいます。ライブラリはメモリの解放の際にはカナリアを確認しますが、もし壊されていたらさらにその外側にあるヒープメモリチェーンも壊れている可能性が高くて続行不能なので、エラー終了します。このバグを見落とすと追跡困難なバグになりやすいので、これは結構有用です。 --[6]確保したメモリの一覧がいつでも表示できるように、情報を貯めます。これで開放し忘れがないか確認できます。この情報は確保したメモリブロックのそばには置きません。なぜならカナリアと一緒に壊される可能性があるからです。もっと別の安全そうな場所に置くことにします。 ** (3) a_ というマクロ -acl4v2 ライブラリでは a_ という引数がちょくちょく現れます。これは「 __FILE__, __LINE__, 」にdefineされていて、a_malloc()などがソースコードの何行目から呼び出されたのかを伝えるための仕組みです。できるだけ目立たないようにするためにこうなっています。 -ちなみに a_DbgLv=0 のときは、 a_ は「」になります。つまり消えます。だってリリースモードなのに余計な引数なんて渡して処理速度が落ちたら嫌ですよね?だからきれいに消えるのです。 -なぜ a_malloc() や a_free() で a_ を渡す必要があるのか説明します。まず a_free() のほうは簡単です。 a_free() でカナリア破壊検出などのエラーがあったときに、それはどの a_free() を呼んだときに気づいたのかわからないと、すごく不便だからです。ああ、〇〇行目の a_free() でこけたかーってわかればたいていすぐに直せます。 -次に a_malloc() のほうです。 a_malloc() はメモリ不足以外では失敗しません。だからエラー個所を特定する必要なんて、ほぼないのです。しかもメモリ不足って、別にどこの行で起きたとか言われてもあまり参考になりません。メモリが尽きたタイミングでどこでも起きますし、起きてしまったら終了する以外にできることはありません(ごくまれにメモリが尽きたら重要度の低いデータを捨てて続行し、重要なデータをセーブさせる、というプログラムを作る人もいますが)。・・・まあ例外として、サイズ指定を間違えたバグなのであれば、エラー行が分かることが役に立ちますが。 -じゃあなぜ a_ をとるのかと言えば、それは確保したメモリブロックに名前を付けるためです。〇〇行目の a_malloc() で確保したサイズ〇〇バイトのメモリってわかると、ああこれのことかーって気づけるんです。そうでないと、メモリアドレスとか言われても、いや、それってどのメモリのことですか?ってなるんです。 ** (4) acl4v2_001.c #if (!defined(a_DbgLv)) #define a_DbgLv 2 #endif #if (!defined(a_NoUse_OutOfMemoryCheck)) #define a_NoUse_OutOfMemoryCheck 0 #endif #if (a_Version > 0) #define DbgLv a_DbgLv #define errExit a_errExit #define DM a_DM #define malloc_ a_malloc #define free_ a_free #define realloc_ a_realloc #define free1 a_free1 #define realloc1 a_realloc1 #define malloc_chkAll a_malloc_chkAll #endif #if (a_DbgLv >= 1) #define a_DbgLv1(x) x #else #define a_DbgLv1(x) #endif #if (a_DbgLv >= 2) #define a_DbgLv2(x) x #define a_ __FILE__, __LINE__, #define a__ __FILE__, __LINE__ #define a_def const char *a_fil, int a_lin, #define a_def_ const char *a_fil, int a_lin #define a_thr a_fil, a_lin, #define a_thr_ a_fil, a_lin #else #define a_DbgLv2(x) #define a_ #define a__ #define a_def #define a_def_ #define a_thr #define a_thr_ #endif //// a_static void a_errExit0(const char *f, ...) { va_list ap; va_start(ap, f); vfprintf(stderr, f, ap); va_end(ap); fprintf(stderr, "\n"); exit(1); } #if (!defined(a_errExit)) #define a_errExit a_errExit0 #endif #define a_DM a_DbgLv2( fprintf(stderr, "%s(%d): DebugMessage\n", __FILE__, __LINE__); ) #define a_malloc_chkAll(flg) a_DbgLv2(a_MallocMgr_chkAll(a_mallocMgr, flg)) //// a_class(a_MallocSub_Node) { intptr_t prv, nxt; }; a_class(a_MallocSub) { void *(*rlc)(void *, size_t); char *p; intptr_t sz, n; }; #define HedUse 0 #define HedFre 1 #define a_MallocSub_Nod(w, i) ((a_MallocSub_Node *) (w->p + i * w->sz)) #define Nod(w, i) a_MallocSub_Nod(w, i) a_static void a_MallocSub_iniHed(a_MallocSub *w, intptr_t i) { Nod(w, i)->prv = i; Nod(w, i)->nxt = i; } a_static void a_MallocSub_add(a_MallocSub *w, intptr_t prv, intptr_t i, intptr_t nxt) { Nod(w, i )->prv = prv; Nod(w, i )->nxt = nxt; Nod(w, nxt)->prv = i; Nod(w, prv)->nxt = i; } a_static void a_MallocSub_rmv(a_MallocSub *w, intptr_t i) { intptr_t prv = Nod(w, i)->prv, nxt = Nod(w, i)->nxt; Nod(w, prv)->nxt = nxt; Nod(w, nxt)->prv = prv; } a_static void a_MallocSub_fre1(a_MallocSub *w, intptr_t i0, intptr_t i1) { intptr_t i; memset(w->p + i0 * w->sz, 0, (i1 - i0 + 1) * w->sz); // ゴミデータが残っていると面倒なのでゼロクリア. for (i = i1; i >= i0; i--) a_MallocSub_add(w, HedFre, i, Nod(w, HedFre)->nxt); } a_static intptr_t a_MallocSub_alc(a_MallocSub *w) { if (Nod(w, HedFre)->nxt == HedFre) { // 未使用Nodeがないので自動拡張. w->p = w->rlc(w->p, w->sz * w->n * 2); if (w->p == NULL) a_errExit("a_MallocSub: out of memory"); a_MallocSub_fre1(w, w->n, w->n * 2 - 1); w->n *= 2; } intptr_t i = Nod(w, HedFre)->nxt; a_MallocSub_rmv(w, i); return i; } //// a_class(a_MallocInf) { a_MallocSub_Node nod[1]; intptr_t sz, lin, no; const char *fil; void *p; }; a_class(a_MallocMgr) { a_MallocSub sub[1]; unsigned char *canary; intptr_t szCanary, no; unsigned char unuseMark; }; a_static a_MallocMgr a_mallocMgr[1]; a_static void a_MallocMgr_ini(a_MallocMgr *w, intptr_t n) { w->sub->rlc = realloc; w->sub->p = malloc(n * sizeof (a_MallocInf)); w->sub->sz = sizeof (a_MallocInf); w->sub->n = n; a_MallocSub_iniHed(w->sub, HedUse); a_MallocSub_iniHed(w->sub, HedFre); a_MallocSub_fre1(w->sub, 2, n - 1); #if (a_DbgLv >= 4) w->szCanary = 1024 * sizeof (intptr_t); #elif (a_DbgLv == 3) w->szCanary = 256 * sizeof (intptr_t); #else w->szCanary = 16 * sizeof (intptr_t); #endif w->canary = malloc(w->szCanary); memset(w->canary, 0xa5, w->szCanary); w->unuseMark = 0x87; w->no = 0; } a_static void *a_MallocMgr_alc(a_MallocMgr *w, a_def intptr_t sz) { #if (a_DbgLv <= 1) (void) w; void *p = malloc(sz); if (p == NULL) a_errExit("a_malloc: out of memory: sz=%d", sz); return p; #else a_MallocSub *v = w->sub; if (w->sub->rlc == NULL) a_MallocMgr_ini(w, 16); if (sz < 0) a_errExit("%s(%d): a_malloc: sz=%d", a_fil, a_lin, (int) sz); char *p = malloc(sizeof (intptr_t) + w->szCanary * 2 + sz); if (p == NULL) a_errExit("%s(%d): a_malloc: out of memory: sz=%d", a_fil, a_lin, (int) sz); char *p1 = p + sizeof (intptr_t); memcpy(p1, w->canary, w->szCanary); char *p2 = p1 + w->szCanary; memset(p2, w->unuseMark, sz); memcpy(p2 + sz, w->canary, w->szCanary); intptr_t i = a_MallocSub_alc(v); *(intptr_t *) p = i; a_MallocSub_add(v, Nod(v, HedUse)->prv, i, HedUse); a_MallocInf *inf = (a_MallocInf *) Nod(v, i); inf->sz = sz; inf->lin = a_lin; inf->fil = a_fil; inf->p = p2; inf->no = w->no++; return p2; #endif } a_static int a_MallocMgr_chk(a_MallocMgr *w, intptr_t i, void *p, intptr_t sz) { a_MallocSub *v = w->sub; a_MallocInf *inf; intptr_t j; if (p == NULL) { if (i < 2 || v->n <= i) a_errExit("a_MallocMgr_chk: err: p=NULL, i=%d", i); inf = (a_MallocInf *) Nod(v, i); p = inf->p; } #if (a_DbgLv >= 4) j = Nod(v, HedUse)->prv; // 新しいものから(=最後尾から)順番に探す. 多分そのほうが速く見つかる. for (;;) { if (j == HedUse) return 1; // 不正ポインタ検出. inf = (a_MallocInf *) Nod(v, j); if (inf->p == p) break; j = inf->nod->prv; } if (j != *(intptr_t *) ((char *) p - w->szCanary - sizeof (intptr_t))) return 2; // malloc情報indexが一致しない#1. #endif if (memcmp((char *) p - w->szCanary, w->canary, w->szCanary) != 0) return 3; // カナリア(ヘッダー)の破壊を検出. j = *(intptr_t *) ((char *) p - w->szCanary - sizeof (intptr_t)); if (i < 2) i = j; if (i < 2 || v->n <= i) return 4; // malloc情報indexが異常値. if (i != j) return 5; // malloc情報indexが一致しない#2. inf = (a_MallocInf *) Nod(v, i); if (p != inf->p) return 6; // ポインタが一致しない(==ID値が壊されている). if (sz < 0) sz = inf->sz; if (sz != inf->sz) return 7; // サイズが一致しない. if (memcmp((char *) p + sz, w->canary, w->szCanary) != 0) return 8; // カナリア(フッター)の破壊を検出. return 0; } a_static intptr_t a_MallocMgr_chkErr(a_MallocMgr *w, a_def intptr_t i, void *p, intptr_t sz, const char *f) { #if (a_DbgLv >= 2) int chk = a_MallocMgr_chk(w, i, p, sz); if (1 <= chk && chk <= 6) a_errExit("%s(%d): %s: bad signature(i=%d, p=0x%x, sz=%d, chk=%d)", a_fil, a_lin, f, (int) i, (int) (intptr_t) p, (int) sz, chk); char *p0 = (char *) p - w->szCanary - sizeof (intptr_t); i = *(intptr_t *) p0; a_MallocInf *inf = (a_MallocInf *) Nod(w->sub, i); if (chk >= 7) { a_errExit("%s(%d): %s: bad signature(i=%d, p=0x%x, sz=%d, no=%d, chk=%d, [%s(%d): sz=%d])", a_fil, a_lin, f, (int) i, (int) (intptr_t) p, (int) sz, (int) inf->no, chk, inf->fil, (int) inf->lin, (int) inf->sz); } #endif return i; } a_static void a_MallocMgr_fre(a_MallocMgr *w, a_def void *p, intptr_t sz) { #if (a_DbgLv <= 1) (void) w; (void) sz; free(p); #else if (p == NULL) return; intptr_t i = a_MallocMgr_chkErr(w, a_thr 0, p, sz, "a_free"); a_MallocInf *inf = (a_MallocInf *) Nod(w->sub, i); char *p0 = (char *) p - w->szCanary - sizeof (intptr_t); memset(p0, w->unuseMark, sizeof (intptr_t) + w->szCanary * 2 + inf->sz); free(p0); a_MallocSub_rmv(w->sub, i); inf->p = NULL; a_MallocSub_add(w->sub, HedFre, i, Nod(w->sub, HedFre)->nxt); #endif } a_static void *a_MallocMgr_rlc(a_MallocMgr *w, a_def void *p, intptr_t sz, intptr_t sz0) { #if (a_DbgLv <= 1) (void) w; (void) sz0; p = realloc(p, sz); if (p == NULL) a_errExit("a_realloc: out of memory: sz=%d", sz); return p; #else if (w->sub->rlc == NULL) a_MallocMgr_ini(w, 16); if (sz < 0) a_errExit("%s(%d): a_realloc: p=0x%x, sz=%d", a_fil, a_lin, (int) (intptr_t) p, (int) sz); if (p != NULL) { intptr_t i = a_MallocMgr_chkErr(w, a_thr 0, p, sz0, "a_realloc"); a_MallocInf *inf = (a_MallocInf *) Nod(w->sub, i); sz0 = inf->sz; if (sz0 == sz) return p; char *q = malloc(sizeof (intptr_t) + w->szCanary * 2 + sz); // サイズが縮小する場合でもアドレスは変わる仕様. if (q == NULL) a_errExit("%s(%d): a_realloc: out of memory: sz=%d", a_fil, a_lin, (int) sz); char *q1 = q + sizeof (intptr_t); memcpy(q1, w->canary, w->szCanary); char *q2 = q1 + w->szCanary; if (sz > sz0) { memcpy(q2, p, sz0); memset(q2 + sz0, w->unuseMark, sz - sz0); } else memcpy(q2, p, sz); memcpy(q2 + sz, w->canary, w->szCanary); *(intptr_t *) q = i; inf->sz = sz; inf->p = q2; char *p0 = (char *) p - w->szCanary - sizeof (intptr_t); memset(p0, w->unuseMark, sizeof (intptr_t) + w->szCanary * 2 + sz0); free(p0); return q2; } return a_MallocMgr_alc(w, a_thr sz); #endif } #if (a_DbgLv <= 1 && a_NoUse_OutOfMemoryCheck != 0) #define a_malloc malloc #define a_free free #define a_realloc realloc #define a_free1(p, sz) free(p) #define a_realloc1(p, sz, sz0) realloc(p, sz) #endif #if (a_DbgLv <= 1 && a_NoUse_OutOfMemoryCheck == 0) #define a_malloc(sz) a_MallocMgr_alc(NULL, sz) #define a_free(p) a_MallocMgr_fre(NULL, p, 0) #define a_realloc(p, sz) a_MallocMgr_rlc(NULL, p, sz, 0) #define a_free1(p, sz) a_MallocMgr_fre(NULL, p, 0) #define a_realloc1(p, sz, sz0) a_MallocMgr_rlc(NULL, p, sz, 0) #endif #if (a_DbgLv >= 2) #define a_malloc(sz) a_MallocMgr_alc(a_mallocMgr, sz) #define a_free(p) a_MallocMgr_fre(a_mallocMgr, p, -1) #define a_realloc(p, sz) a_MallocMgr_rlc(a_mallocMgr, p, sz, -1) #define a_free1(p, sz) a_MallocMgr_fre(a_mallocMgr, p, sz) #define a_realloc1(p, sz, sz0) a_MallocMgr_rlc(a_mallocMgr, p, sz, sz0) #endif a_static void a_MallocMgr_chkAll(a_MallocMgr *w, a_def int flg) { #if (a_DbgLv >= 2) fprintf(stderr, "%s(%d): a_MallocMgr_chkAll: a_DbgLv=%d\n", a_fil, a_lin, a_DbgLv); a_MallocSub *v = w->sub; intptr_t i = Nod(v, HedUse)->nxt, n = 0, total = 0, nChk = 0; for (;;) { if (i == HedUse) break; a_MallocInf *inf = (a_MallocInf *) Nod(v, i); int chk = a_MallocMgr_chk(w, i, NULL, -1); if ((flg & 1) != 0 || chk != 0) { fprintf(stderr, " #%04d: chk=%d [i=%04d p=0x%08x sz=%04d no=%04d : %s(%d)]\n", (int) n, chk, (int) i, (int) (intptr_t) inf->p, (int) inf->sz, (int) inf->no, inf->fil, (int) inf->lin); if (chk != 0) nChk++; } total += inf->sz; i = Nod(v, i)->nxt; n++; } fprintf(stderr, " n=%d, total=%d, nChk=%d\n", (int) n, (int) total, (int) nChk); if (nChk > 0) exit(1); #endif } #undef HedUse #undef HedFre #undef Nod ** (5) 解説 -''[5-1]'' まず最初に確認してもらいたいのは、最後のほうにある読み替え用の define です。 a_DbgLv<=1 で、かつ a_NoUse_OutOfMemoryCheck!=0 であれば、以下のようになります。 #define a_malloc malloc #define a_free free #define a_realloc realloc #define a_free1(p, sz) free(p) #define a_realloc1(p, sz, sz0) realloc(p, sz) -つまりここに書かれた関数はほぼ全部使わないことになって、標準関数をそのまま使うという、まったくオーバーヘッドのない形になります。 -これに対して、 a_DbgLv>=2 であれば、以下のような読み替えになります。これでまずどれがどの役割を持つのか把握しておくと理解しやすいはずです。 #define a_malloc(sz) a_MallocMgr_alc(a_mallocMgr, sz) #define a_free(p) a_MallocMgr_fre(a_mallocMgr, p, -1) #define a_realloc(p, sz) a_MallocMgr_rlc(a_mallocMgr, p, sz, -1) #define a_free1(p, sz) a_MallocMgr_fre(a_mallocMgr, p, sz) #define a_realloc1(p, sz, sz0) a_MallocMgr_rlc(a_mallocMgr, p, sz, sz0) -ついでなので、ここで a_free1() や a_realloc1() について説明しておこうと思います。これらは標準関数にはない関数ですが、簡単に言ってしまえば free() するときにサイズ指定をするだけの関数です( realloc でも旧サイズを申告します)。このとき、好き勝手なサイズを指定できるわけではなく、 malloc() したときのサイズを正しく申告する必要があります。 -これは malloc() を自作してみるとわかりやすいのですが、もし free するときにアプリ側が確実にサイズを教えてくれるのだとしたら、 malloc 側でサイズを覚えておく必要が無くなって処理を簡略化できるのです(処理の簡略化だけではなく、メモリの利用効率も改善します)。それで、自作OSではそういう仕様の malloc() を書いたことがあります。アプリが free 時にサイズを申告するのは面倒だと思うかもしれませんが、アプリはほとんどの場合で free するとき解放領域のサイズを知っているので、やってみると大した手間ではありません。これはこれで十分にアリな仕様だと感じました(もちろん簡単にはわからない場合もあります。その場合は malloc の時に指定したサイズを記録する処理をアプリ側が書くことになります)。 -それで、普段から free1() や realloc1() でアプリを書いておけば、どちらの環境でも動くアプリが簡単に書けるので、私はこれらの関数も準備しておくのが好きなのです。 -''[5-2]'' それでじゃあ、まあ最初から眺めていくと、 a_errExit() までは今までの説明でなんとなくわかると思います。 a_errExit() では、直接この関数を書かずに、 a_errExit0() を作って、 a_errExit() を a_errExit0() に読み替えさせるような回りくどいことをしています。なぜこんなことをしているのか説明したいです。 -この a_errExit() 関数は、基本的にエラーメッセージを表示して exit(1) するだけの取るに足らない関数です。こんな関数をわざわざ作る必要があるのか?毎回 fprintf() して exit(1) すればいいだけじゃないかと言われると、実はその通りです。でもこういう些細な関数こそ、ライブラリ自作では大活躍します。 -逆に、なんか高度なことをいろいろやってくれる大規模な関数は、活躍できる機会があまり多くありません。つまり小さな基本部品から大きな部品をいきなり作るのではなく、たくさんの階層に分けて少しずつ作っていくべきなのです。・・・そういうことが分かってから標準関数をみると、 strlen() とか strcpy() とか、とても小規模な関数が多いと気付かされます。こういう作り方がうまいのです。・・・ライブラリ自作で大事なことは、自作したライブラリ関数をできるだけたくさん使うことです。使わないならライブラリ自作駆動開発の前提が崩れてしまいます。些細な関数のほうが使う機会が増えるのなら、些細な関数こそ作られるべきなのです。 -ということで a_errExit() は作られるべき関数の代表みたいなものなのですが、しかし常にこのエラー処理でいいのかという迷いがあります。例えばグラフィカルなアプリだったら、標準エラー出力ではなく専用のメッセージボックスに表示したいかもしれません。そういう場合、 a_errExit() を自前のエラー処理関数に差し替えたくなります。だから必要になったら簡単に差し替えばできるように、読み替え方式になっているのです。 -''[5-3]'' 次は DM関数です。これは DebugMessage の略です。・・・これは、ええと、プログラムをデバッグしているときに、「あれ?ここは通ったかな?」という確認をするために printf("hello\n"); とか printf("hello2\n"); とかやることがありませんか?(もしかして今どきのプログラマこんなことはしないのかな?)。この hello とかを考えるのが面倒なので、そういうときはただ DM と書くのです。かっこもセミコロンもいりませんし、 a_DbgLv<=1 のときは自動で消えてくれます。「hello」のようなメッセージ内容は指定できないのですが、代わりに行番号が出るので、むしろめんどくさいことを考えなくてよくて重宝します。 -''[5-4]'' 次は a_MallocSub クラスです。・・・まずこの名前はあまりよくありません。 malloc のために作ったクラスではありますが、それ以外の用途に使えないということはなく、だからもっといい名前を付けるべきでした。それは分かっていたのですが、いいクラス名を思いつくことができなくて、迷っている時間がもったいないのでこの名前になりました。 -この辺りからポインタを多用していて、なんかよくわからないなーってなる人もいると思います。そういう時は''無理して理解しようとはしないで、何をしてくれる関数なのかということだけ理解すれば十分だと思います。''だってそもそも標準関数だって、使い方を理解できればそれで十分に使えますよね。本来、ライブラリは実装の細部を理解する必要はないのです。 -このクラスは、「双方向循環リスト」というデータ構造を使って malloc 情報を登録順に並べて管理しています。 --双方向循環リストはここに説明があります: https://ja.wikipedia.org/wiki/%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88 -イメージとしては a_MallocInf の配列があって、それぞれの inf には malloc 情報のほかに、「前(prv)」と「次(nxt)」という情報があって、これを使って、inf[5]の次はinf[8]、みたいに登録順序をたどっていくことができます。prvがあるので最後尾から始まって逆順にたどっていくこともできます。 -なぜ双方向循環リストを使うのか?・・・単純に考えたら、inf[0],inf[1],inf[2]の順番に並べておけばよさそうに思えます。しかし1000個くらい登録された状態で、inf[4]のメモリブロックが free されたら、inf[5]~inf[999]を全部ずらさなければいけません。このずらし処理には memmove() を使いますが、転送量が多いと結構時間がかかります。ずらし処理をしたくなかったので、prvとnxtのある双方向循環リストにしたのです。これならずらす必要がありません。prvとnxtをつなぎ変えるだけで inf[4] を切り離すことができます。この切り離し処理は a_MallocSub_rmv() がやっています。 -一方、 malloc したときには inf[i] を接続しなければいけません。接続処理は a_MallocSub_add() がやっています。 -実は a_MallocSub クラスでは、2つの双方向循環リストを持っています。一つは a_MallocInf を登録順に並べたものですが、もう一つは何の情報も入っていない空(から)の inf[] を集めて並べて置いたものです。配列内のすべての inf[] は必ずこのどちらかの双方向循環リストに所属することになっています。・・・なぜ空の inf[] をつないでおく必要があるのかというと、それがないと malloc して新規登録しなければいけないときに、未使用の inf[] を探すのが大変になるからです。双方向循環リストから探す方法なら一瞬でできます。 -実は未使用管理のほうは、双方向にする必要はなくて片方向でも十分に目的を果たせるとわかっていたのですが、わざわざそのために片方向ようの関数を書くのがめんどくさいということになり、そのまま未使用管理でも双方向で管理するようにしています。 -a_MallocSub_iniHed(w, i): inf[i]を双方向循環リストの番兵として初期化します。 -a_MallocSub_add(w, prv, i, nxt): inf[i]を双方向循環リストに加えます。iから見て、prvとnxtに当たる配列番号も指定します。 -a_MallocSub_rmv(w, i): inf[i]を双方向循環リストから切り離します。 -a_MallocSub_fre1(w, i0, i1): inf[i0]からinf[i1]を未使用リストに追加します。初期化のときに使います。 -a_MallocSub_alc(w): 未使用リストから1つとってきます。もし未使用が一つもないときは realloc() で配列を拡張して未使用を作って、それから未使用を1つ返します。 -''[5-5]'' 次は a_MallocMgr クラスです。 malloc のマネジャーです。 -これはメモリを確保するときに、[inf配列の番号][カナリア(ヘッダー)][szバイト][カナリア(フッター)]になるように工夫した malloc です。アプリには sz バイトの部分を渡します。アプリはバグによって与えられた範囲をはみ出してしまう可能性があるので、前後をカナリアで挟んで保護している感じです。ちょっとのはみだしで済めば、他に悪影響を与えることはないわけです。 -a_MallocMgr_ini(w, n): a_mallocMgr を初期化します。a_DbgLvの値に応じてカナリアの大きさを変えています。a_DbgLv>=4の場合、カナリアは4KB以上になります。おおきい!! -a_MallocMgr_alc(w, sz): a_malloc() の処理をします。 -a_MallocMgr_chk(w, i, p, sz): iもしくはpで指定されたメモリブロックが壊れていないかテストします。壊れていなければ0を返し、壊れていたら1以上の値を返します。 -a_MallocMgr_chkErr(w, i, p, sz, f): 指定されたメモリブロックを chk() で調査して、もし壊れていればエラーメッセージを出して強制終了します。 -a_MallocMgr_fre(w, p, sz): a_free() の処理をします。 -a_MallocMgr_rlc(w, p, sz, sz0): a_realloc() の処理をします。 -a_MallocMgr_chkAll(w, flg): a_malloc() で確保済みのメモリについて、すべて chk します。flg=1の場合は正常なものも異常なものも報告します。flg=0の場合は、異常なもののみ報告します。 -このプログラムでは intptr_t を int にキャストしてから printf しています。こうすることで64bit環境などでは、上位bitが落ちて、下位bitしか見えなくなるかもしれません。そこをちゃんとしようとすると結構大変なので、今回は妥協しました。まあデバッグ用途であれば、下位bitのみの表示でも困ることはあまりないのではないかと思います。 ** (6) サンプルコード sample001a.c -以下のプログラムで試してみました。 #include <acl4v2.c> int main(int argc, const char **argv) { char *a = malloc_(a_ 10); char *b = malloc_(a_ 20); char *c = malloc_(a_ 30); free_(a_ b); strcpy(a, "0123456789"); // 10文字だから大丈夫・・・と思いきや、これをやると最後の'\0'がはみ出してしまいます. malloc_chkAll(a_ 1); // プログラム終了前にこれを書くくせをつけておくと、メモリリークがどんどん見つかります. return 0; } -実行結果は以下の通りです。 sample001a.c(10): a_MallocMgr_chkAll: a_DbgLv=2 #0000: chk=8 [i=0002 p=0xfd084a98 sz=0010 no=0000 : sample001a.c(5)] #0001: chk=0 [i=0004 p=0xfd084ce8 sz=0030 no=0002 : sample001a.c(7)] n=2, total=40, nChk=1 -mallocされたがまだfreeしてないメモリは2つあり、それがaとcのことだとすぐにわかります(行番号が分かるので)。 -またaのほうはカナリア(フッター)が壊れているということもわかります。 -sample001a.c くらいの記述量で、ここまで情報が取れるっていうのは、結構いい感じだと私は思います。これなら今後は acl4v2 の中で暮らしてもいいかなーと。 -これはOSやCPUに依存するデバッガではないので、どんな環境でも同じように使えるというところも気に入っています。 ** (7) サンプルコード sample001b.c -以下のプログラムで試してみました。 // #define a_DbgLv 4 #include <acl4v2.c> int main(int argc, const char **argv) { char *a = malloc_(a_ 10); char *b = malloc_(a_ 20); char *c = malloc_(a_ 30); free_(a_ a); free_(a_ b); free_(a_ a); // ここで間違えて、二重freeをしている. malloc_chkAll(a_ 1); // プログラム終了前にこれを書くくせをつけておくと、メモリリークがどんどん見つかります. return 0; } -実行結果は以下の通りです。 sample001b.c(11): a_free: bad signature(i=0, p=0xf3b5aea8, sz=-1, chk=3) -二重freeのところでエラー終了したことが分かります。 chk=3 なのでカナリア(ヘッダー)が壊れているという扱いになっていることが分かります。これは一度目の正しいfreeで、カナリアをわざと壊しているからです。カナリアをあえて壊すのは、こうやって二重freeをしようとしたときに検出できるようにするためです。 -ちなみに a_DbgLv=4 でビルドすると、エラー終了の時の理由が変わって chk=1 になります。これはもはやカナリアを確認するまでもなく、指定されたポインタが inf[] の使用中リストの中に入ってないから、カナリア部分をメモリアクセスしなくてもおかしいってわかったよ、というエラーです。 ** (8) あとがき -普通 mallocやfreeの自作をするとなったら、自作mallocはこんなに高速なんだぜ!って性能自慢をすると思います。私だってそういう時期がありましたよ。・・・でも acl4v2 ではそんな話は全く出てこなくて、最初から最後まで、デバッグ支援機能の話ばかりです。・・・でも考えてみてください。開発効率(開発速度)を上げていこうと思ったら、まず最初にやるべきはデバッグ支援だと私は思うのです。デバッグのために費やされる時間に何の意味があるでしょう。そんなのはできるだけ少なくするべきなんです。私は、そしてこれを読んでいる皆さんも、きっと将来はスーパーなプログラマになります(予定)。スーパーなプログラマは、自分にしかできない開発をどんどんやってみんなを引っ張っていかなければいけません!(笑)。 malloc の高速化なんて、できそうな人はまあまあいそうです。だからその人たちに任せればいいのです。私たちは、それ以外の場所をさっさと作りましょう。速くなったらどんなによくなるかなんて、別に私たちがやらなくてもどんなものなのか想像できます。でも新しい機能は、私たちが作って見せないとわからないじゃないですか。そんな時、デバッグに足を引っ張られていたらもったいないのです! -ちなみにスーパーなプログラマがいい機能をどんどん作れる最大の秘訣は、思いついたものを次々と作ることです。結局私たちだって、作って確認しないとわからないんです。だからどんどん作りましょう。失敗作もたくさんできますが、いろいろ作っていいものだけを世に出すので、「なんかこいつスゲーな!」になるんです。 * こめんと欄 #comment
テキスト整形のルールを表示する