* 川合のプログラミング言語自作のためのテキスト第四版#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

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS