acl4のページ0002

  • (by K, 2026.01.24)

(1)

#if (a_Version >= 1)
    #define List            a_List
    #define List_ini        a_List_ini
    #define List_addTl      a_List_addTl
    #define List_addHd      a_List_addHd
    #define List_rmv        a_List_rmv
#endif

a_class(a_List) { a_List *prv, *nxt; };
a_static void a_List_ini(a_List *w) { w->prv = w->nxt = w; }

a_static void a_List_add(a_List *prv, a_List *tgt, a_List *nxt)
{
    prv->nxt = tgt;
    tgt->prv = prv;
    tgt->nxt = nxt;
    nxt->prv = tgt;
}

a_static void a_List_rmv(a_List *prv, a_List *nxt) { prv->nxt = nxt; nxt->prv = prv; }
a_static void a_List_addTl(a_List *w, a_List *tgt) { a_List_add(w->prv, tgt, w); }
a_static void a_List_addHd(a_List *w, a_List *tgt) { a_List_add(w, tgt, w->nxt); }

#if (a_Version >= 1)
    #define malloc_     a_malloc
    #define free_       a_free
    #define realloc_    a_realloc
#endif

a_class(a_malloc_Header) {
    a_List lst[1];
    intptr_t dmy[1], sz, lin;
    const char *fil;
    intptr_t serialNum, sig;
};

a_malloc_Header a_malloc_hed0[1];
#define a_malloc_Sig    0xff0055aa

a_static void *a_malloc(_argDef_  intptr_t sz)
{
    #if (a_DbgLv < 2)
        void *p = malloc(sz);
        if (p == NULL)
            a_errExit("malloc_: out of memory: sz=%d");
        return p;
    #else
        if (sz < 0)
            a_errExit("%s(%d): malloc_ size error: sz=%d", a_fil, a_lin, (int) sz);
        a_malloc_Header *h = (a_malloc_Header *) malloc(sz + sizeof (a_malloc_Header));
        if (h == NULL)
            a_errExit("%s(%d): malloc_: out of memory: sz=%d", a_fil, a_lin, (int) sz);
        if (a_malloc_hed0->lst->prv == NULL) {
            a_List_ini(a_malloc_hed0->lst);
            a_malloc_hed0->sz = -1;
            a_malloc_hed0->sig = a_malloc_Sig;
            a_malloc_hed0->lin = 0;
        }
        a_List_addTl(a_malloc_hed0->lst, h->lst);
        h->sz = sz;
        h->sig = a_malloc_Sig;
        h->lin = a_lin;
        h->fil = a_fil;
        h->serialNum = ++(a_malloc_hed0->serialNum);
        a_malloc_hed0->lin++;
        return h + 1;
    #endif
}

a_static void a_free(_argDef_  void *p, intptr_t sz)
{
    #if (a_DbgLv < 2)
        (void) sz;
        free(p);
    #else
        if (p == NULL) return;
        a_malloc_Header *h = (a_malloc_Header *) p - 1;
        if (h->sig != a_malloc_Sig)
            a_errExit("%s(%d): free_: bad signature", a_fil, a_lin);
        if (h->sz != sz)
            a_errExit("%s(%d): free_: bad size: sz=%d, arg=%d", a_fil, a_lin, (int) h->sz, (int) sz);
        h->sig = 0;
        h->serialNum = 0;
        a_List_rmv(h->lst->prv, h->lst->nxt);
        a_malloc_hed0->lin--;
        free(h);
    #endif
}

a_static void *a_realloc(_argDef_  void *p, intptr_t sz0, intptr_t sz1)
{
    #if (a_DbgLv < 2)
        (void) sz0;
        p = realloc(p, sz1);
        if (p == NULL)
            a_errExit("realloc_: out of memory: sz1=%d");
        return p;
    #else
        if (sz1 < 0)
            a_errExit("%s(%d): realloc_ size error: sz1=%d", a_fil, a_lin, (int) sz1); 
        if (p == NULL)
            return a_malloc(a_fil, a_lin, sz1);
        a_malloc_Header *h = (a_malloc_Header *) p - 1;
        if (h->sig != a_malloc_Sig)
            a_errExit("%s(%d): realloc_: bad signature", a_fil, a_lin);
        if (h->sz != sz0)
            a_errExit("%s(%d): realloc_: bad size: sz=%d, arg=%d", a_fil, a_lin, (int) h->sz, (int) sz0);
        h->sig = 0;
        intptr_t sn = h->serialNum;
        h->serialNum = 0;
        a_List *prv = h->lst->prv;
        a_List *nxt = h->lst->nxt;
        a_List_rmv(prv, nxt);
        a_malloc_Header *h1 = (a_malloc_Header *) realloc(h, sz1 + sizeof (a_malloc_Header));
        if (h1 == NULL)
            a_errExit("%s(%d): realloc_: out of memory: sz1=%d", a_fil, a_lin, (int) sz1);
        a_List_add(prv, h1->lst, nxt);
        h1->sig = a_malloc_Sig;
        h1->serialNum = sn;
        h1->sz = sz1;
        h1->lin = a_lin;
        h1->fil = a_fil;
        return h1 + 1;
    #endif
}

a_static intptr_t a_malloc_totalSiz(_argDef_  intptr_t *pn)
{
    intptr_t s = 0;
    #if (a_DbgLv >= 2)
        (void) a_fil; (void) a_lin;
        if (a_malloc_hed0->lst->prv != NULL) {
            a_List *t = a_malloc_hed0->lst->nxt;
            for (;;) {
                a_malloc_Header *h = (a_malloc_Header *) t;
                if (h->sz < 0) break;
                s += h->sz;
                t = t->nxt;
            }
        }
    #endif
    if (pn != NULL) *pn = a_malloc_hed0->lin;
    return s;
}

a_static void a_malloc_debugList(_argDef)
{
    #if (a_DbgLv >= 2)
        fprintf(stderr, "%s(%d): malloc_debugList()\n", a_fil, a_lin);
        if (a_malloc_hed0->lst->prv != NULL) {
            a_List *t = a_malloc_hed0->lst->nxt;
            for (;;) {
                a_malloc_Header *h = (a_malloc_Header *) t;
                if (h->sz < 0) break;
                fprintf(stderr, "  [p:%08x sz:%d / %s(%d)]\n", (int) (intptr_t) (h + 1), (int) h->sz, h->fil, (int) h->lin);
                t = t->nxt;
            }
        }
    #endif
}

(2) List

  • 私の大好きな双方向リストを簡単に扱うためのライブラリ関数群です。
  • まず a_class(a_List) { a_List *prv, *nxt; }; で構造体を宣言しています。
  • 最初なので、これがどう展開されるかを説明します。
    typedef struct a_List ## _ a_List;
    struct a_List ## _ { a_List *prv, *nxt; };
  • となったあと、
    typedef struct a_List_ a_List;
    struct a_List_ { a_List *prv, *nxt; };
  • となるのです。これで自前でtypedefしなくてもa_Listをstructなしで書けるようになるわけです。
  • 私はよく使う語に関しては短く書くのが好きです。
    • prv → previous
    • nxt → next
    • ini → init
    • ins → insert
    • rmv → remove
    • tgt → target
    • Tl → Tail
    • Hd → Head
  • またクラスのthisポインタのことをwと略します。これは私がオブジェクトのことをworking-area(作業領域)と呼んでいたころの名残りです。
  • こんな簡単な処理くらい、関数化しないで手書きしてもいいですし実際にそうすることもありますが、たまに間違えてデバッグで苦労するので、関数で処理することが多いです。まあたぶんほとんどインライン展開されていて、手書きと同じ速度になっているような気はします。
  • addTl, addHdの第一引数wは番兵用のノードで、絶対に削除されることのないノードです。たいていの場合は有効な値を持たないノードですが、値を持たせてもいいです。
  • この番兵ノードのnxtが双方向リストの先頭で、番兵ノードのprvが双方向リストの末尾になります。

(3) malloc_, free_, realloc_

  • まず a_DbgLv が 0 だったらどうなるかを確認してください。
  • これらは malloc, free, realloc に out of memory 用のチェックがついただけのラッピング関数でしかなく、ほぼ長所はありません。
  • さて、では a_DbgLv が 2 だった場合を見てみましょう。
  • まず malloc ではサイズ指定が符号付きの整数になっていて、負の場合はエラーが出るようになります。
  • そもそも私は、size_t型がunsignedであるのは下手な設計だとずっと思っています。サイズ計算しようと思ってポインタの引き算をするとき、 sz=p1-p0; を sz=p0-p1; と書き前違えてしまうことは十分に想定できます。そうするとszの値は当然マイナスになりますが、size_tは負の数を受け付けないので、結果的に途方もなく大きな正の値になります。こうなってしまっては、引き算のミスを見つけにくくなります。
  • size_tがunsignedなのは、メモリ領域のサイズとして2GB以上も指定できるようにしたかったのでしょう(32bitの場合)。しかし2GB以上のメモリ領域が必要なことなんてものすごくレアです。サイズ計算ミスの頻度のほうがはるかに高いです。だから私は malloc のサイズ指定は符号付きの整数値であるべきだと思っていますし、デバッグレベルが2以上なら、szが負になっていないかどうかチェックして、負だったら止まるべきだと思っているのです。
  • mallocではシグネチャやシリアルナンバーをヘッダに記入する処理が追加されます。
  • freeではヘッダの健全性をチェックする処理が追加されます。free時にはシグネチャとシリアルナンバーをわざわざ0クリアしてからfreeしています。どうせfreeするんだから、そんなことしても意味ないと思うかもしれません。いやいや、こうすれば謝って二重freeしたときにエラーを出せるようになるのです。
  • シリアルナンバーは、この領域をfreeしてmallocで偶然に再利用されたときに、値が変わって気づけるようにするためのものです。再利用されてしまうとシグネチャだけでは判断できないのです。
  • このプログラムではまだシリアルナンバーを全く利用できてないですが、malloc時にシリアルナンバーを控えておいて、それが変わらないことを監視すれば、use-after-free脆弱性を検出できるようになります。
  • mallocでは引数の最初に、 _argDef_ という見慣れないものがついています。これはa4_0001で定義されていて、 const char *a_fil, int a_lin, になります(DbgLv>=2なら)。
  • これは何かというと、 malloc_ を呼び出すときに _arg_ をつけるのです。そうすると、__FILE__, __LINE__, になるので、ソースコードのどこから malloc_ を呼び出したのかわかるようになります。この情報があると、エラーの時に原因を突き止めるのがだいぶ楽になります。 free_ や realloc_ も同様です。
  • acl4の free_ ではサイズ指定が必要です。これで「領域Aを開放しているつもりだったのにプログラムは領域Bを指定していた」みたいなバグに速く気付くことができます(サイズ不一致エラーで気づく)。
  • a_malloc_totalSiz と a_malloc_debugList は、これをこのまま使うというよりは、どうすればメモリ使用状況の情報を取ってこられるかのメモ代わりに作ったものです。これを参考に、メモリリークがないかどうかを確認するプログラムを作れます。

(4) 使い方

  • acl4ライブラリは以下のようにして利用します。
  • [1]まずa4_0001~a4_0002のプログラムをつなげて"acl4.c"として保存します。
  • [2]次にacl4を使ったプログラムを書きます。
  • [3]コンパイル時に、"acl4.c"のあるパスをインクルードパスの一つとして指定します。
  • 以下のコードでやってみます。"t0002a.c"とします。
#define a_Version 1
#include <acl4.c>

int main()
{
    char *p = malloc_(_arg_  12);
    char *q = malloc_(_arg_  34);
    char *r = malloc_(_arg_  56);
    char *s = malloc_(_arg_  78);
    free_(_arg_ q, 34);
    r = realloc_(_arg_  r, 56, 90);
    a_malloc_debugList(_arg);
    return 0;
}
>t0002a
t0002a.c(12): malloc_debugList()
  [p:00d2cb38 sz:12 / t0002a.c(6)]
  [p:00d330e8 sz:90 / t0002a.c(11)]
  [p:00d33070 sz:78 / t0002a.c(9)]

(5)

  • 私の信条としては、C言語の標準関数が最初からこのくらいの機能を持っていれば、ガーベージコレクションは開発されなかったかもしれないし、C++のスマートポインタやRustの所有権の概念も生まれなかったのではないか、と思っています。
    • いや、生まれたかもしれないけど、ここまで活用されなかったのではないか、くらいのニュアンスです。
  • こんな簡単な仕組みがあればそれで十分だったのです。
  • バグに早く気付くことができれば、複雑な仕組みがなくても何とかなると思うんです。

(99) 更新履歴

  • 2026.01.24(土) 初版。
  • 2026.01.30(金) クラス名変更: DoublyLinkList → List

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2026-01-30 (金) 10:03:41 (137d)