acl4のページ0016

  • (by K, 2026.04.23)

(1) 概要

  • a4_0016 は、 acl4v1 にとっての一番最初のライブラリプログラムになります。
  • 提供される主な機能は、以下の通りです。
    • (1-1)主要な標準関数をすべてインクルード
    • (1-2)便利関数・便利マクロ(static_, class_, errExit, List)
    • (1-3)デバッグ支援関数(malloc_, free_, realloc_, DbgObjInf)
  • 以下、それぞれについてもう少し詳しく説明します。


  • (1-1)主要な標準関数をすべてインクルード
    • 標準関数を使う際にわざわざ毎回 include を書くのは面倒なので、この機能を付けました。<acl4v1.c>さえインクルードすれば、主要な標準関数はすべて使える状態になります。
    • このためだけに acl4 を使うこともあるくらいです(笑)。
  • (1-2)便利関数・便利マクロ(static_, class_, errExit, List)
    • [static_] acl4 ではすべての関数に static 属性がデフォルトでつきます。これはあえて分割コンパイルを想定せず、全体を一つの.cにすることでコンパイラに大域での最適化をやってもらおうと考えていて、その際に、使わない関数は実行ファイルに含めずに全部消してほしいと思っているからです。
    • かつては私も分割コンパイル推進派でしたが、CPUの性能が上がりメインメモリもかなり大きくなってきて、(私が作るプログラムの規模なら)分割しないでも十分にやっていけるし、むしろそのほうがメリットが大きいと感じています。
    • しかしもしかしたら分割コンパイルをやりたいと思うことがあるかもしれません。その時に acl4 が全く使えないということになったら困るので、 a_static というマクロが用意されています。これを事前に空文字で定義しておけば、すべての関数に static 属性がつかなくなります。
    • [class_] C言語には struct で構造体を宣言する機能があります。 struct MyStr { ... }; と書けばそれで十分なのですが、しかしこれだと使うたびに struct MyStr myObj; みたいに「struct」をその都度書く必要があり、とても邪魔です。C++なら省略できるのに・・・。
    • Cでも typedef で宣言しておけば「使うときの struct 」を書かなくてよくなるのですが、構造体を宣言するたびに typedef をやるというのも手間なので、それを自動でやってくれるのが class_ です。
    • [errExit] 「エラーメッセージを stderr に出力して終了」という処理は結構よく出てきます。fprinttf(stderr, ...); exit(1); ってすればいいだけなんですが、こうなると if 文で { } が必要になりますし、なんか面倒だなって思うのです。だから errExit() という関数を作って、1文で書けるようにしました。ただそれだけの便利関数です。
    • [List] 私は双方向リストが好きなのですが、たまにつなぎ間違えてデバッグに苦労するので、便利関数群としてここに入れています。
  • (1-3)デバッグ支援関数(malloc_, free_, realloc_, DbgObjInf)
    • [malloc_] malloc_ や realloc_ は、NULLを返しません。メモリが尽きた場合はエラーメッセージを表示して勝手に終了します。この仕様のため「メモリがなければ処理をあきらめる」みたいなことはできません。その代わりアプリプログラム側で malloc_ が NULL を返したらどうしよう、みたいなケースを想定する必要はありません。
    • こういう仕様なのは、もはやメモリ不足を起こしてしまうのは普通のプログラムでは起きないことで、単純にバグが原因になっているので、それらならエラーメッセージを出して exit(1); するほうが有用だろうと思っているからです。エラーメッセージではエラーを起こした malloc_ がソースコード上で何行目にあるか、要求したサイズはいくつなのかも表示されます。
    • malloc_ で得られたメモリ領域は、(デバッグモードであれば) 0x87 で塗りつぶされた状態で渡されます。未初期化であることをわかりやすくするためです。リリースモードの際はこのような追加の処理は行いません。
    • [free_ ] 当たり前ですが、 malloc_ や realloc_ で得たポインタは free_ できますが、そうではないポインタを free_ するとエラー終了します。このチェックがあるので、ポインタのミスでデバッグに苦労することはほとんどなくなりました。二重 free も検出されます。
    • free_ の際には中身を全部 0x87 で塗りつぶしてから free するようにしています。これにより、 use-after-free みたいなミスをしたときにすぐに気づけます(誤ってfree後に読みだすと、元のデータが読めないので気づく)。
    • [DbgObjInf] オブジェクトの init/deinit の対応状態を管理しています。initをしてないのに deinit したとか、 initを2回しているとか、deinitを2回しているなどのバグを見つけて報告してくれます。
    • malloc_ や DbgObjInf は、現在有効になっているもののリストを内部で持っているので、プログラム終了時にこれを確認することで、メモリリークやdeinit忘れなどを簡単に見つけることができます。

(2) デモ

  • わざとfreeをやり忘れたり、deinitをやり忘れたりして、どのように表示されるかを示します。
  • 手ごろなクラスがなかったので a4_0017 の内容を先取りして、 VecChr を使っています。
  • [t0016a.c]:
    #define a_Version   1
    #include <acl4v1.c>
    
    int main(void)
    {
        char *a[5];
        a[0] = malloc_(_a_ 1); // このように malloc/free/init/deinitの際には、 _a_ を付ける.
        a[1] = malloc_(_a_ 3);
        a[2] = malloc_(_a_ 5);
        a[3] = malloc_(_a_ 7);
        a[4] = malloc_(_a_ 9);
        free_(_a_ a[1], 3);
        free_(_a_ a[3], 7);
        VecChr vc[1]; VecChr_ini(_a_ vc);
        a_malloc_debugList(_a); // a[0], a[2], a[4]は未開放なので、ここで検出したい.
        a_DbgObjInfTbl_debugList(_a); // vcは未開放なので、ここで検出したい.
        return 0;
    }
  • 実行結果:
    >t0016a
    t0016a.c(15): malloc_debugList()
      [p:012dccb0 sz:1 / t0016a.c(7)]
      [p:012dc868 sz:5 / t0016a.c(9)]
      [p:012dc8c8 sz:9 / t0016a.c(11)]
    t0016a.c(16): DbgObjInfTbl_debugList()
      [VecChr / t0016a.c(14)]
  • 注意点として、a_malloc_debugList()で表示されるpの値は、実際のアドレスの下位32bitのみなので、64bit環境下ではpの値そのものではありません。
  • しかしデバッグにおいては、64bitすべてが表示されていなくても下位32bitが分かるだけで十分なので、そのままにしています。

(3) ライブラリプログラム

#if (!defined(a_DbgLv))
    #define a_DbgLv     2
#endif
#if (!defined(a_Version))
    #define a_Version   9999
#endif

#if (a_DbgLv < 1)
    #define a_DbgLv1(x)
#else
    #define a_DbgLv1(x) x
#endif

#if (a_DbgLv < 2)
    #define _aDef_
    #define _aDef
    #define _a_
    #define _a
    #define _aThr_
    #define _aThr
    #define a_DbgLv2(x)
#else
    #define _aDef_      const char *a_fil, int a_lin,
    #define _aDef       const char *a_fil, int a_lin
    #define _a_         __FILE__, __LINE__,
    #define _a          __FILE__, __LINE__
    #define _aThr_      a_fil, a_lin,
    #define _aThr       a_fil, a_lin
    #define a_DbgLv2(x) x
#endif

#include <ctype.h>
#include <errno.h>
#include <float.h>
#include <inttypes.h>
#include <limits.h>
#include <locale.h>
#include <math.h>
#include <setjmp.h>
#include <signal.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#if (a_Version >= 1)
    #define static_         a_static
    #define class_          a_class
    #define DbgLv           a_DbgLv
    #define DM		     a_DM
    #define DbgLv1          a_DbgLv1
    #define DbgLv2          a_DbgLv2
    #define errExit         a_errExit
    #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
    #define malloc_         a_malloc
    #define free_           a_free
    #define realloc_        a_realloc
    #define DbgObjInf       a_DbgObjInf
    #define DbgObjInf_ini   a_DbgObjInf_ini
    #define DbgObjInf_din   a_DbgObjInf_din
#endif

#if (!defined(a_static))
    #define a_static    static
#endif
#define a_class(nam)    typedef struct nam ## _ nam; struct nam ## _

a_static void a_errExit(const char *f, ...)
{
    va_list ap;
    va_start(ap, f);
    vfprintf(stderr, f, ap);
    va_end(ap);
    fprintf(stderr, "\n");
    exit(1);
}

#define a_DM	a_DbgLv2( fprintf(stderr, "%s(%d): DebugMessage\n", __FILE__, __LINE__); )

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); }

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

a_static a_malloc_Header a_malloc_hed0[1];
#define a_malloc_Sig    0xff0055aa

a_static void *a_malloc(_aDef_  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);
        memset(h, 0x87, sz + sizeof (a_malloc_Header));
        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(_aDef_  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; // memsetがあるからこれはいらない.
        a_List_rmv(h->lst->prv, h->lst->nxt);
        a_malloc_hed0->lin--;
        memset(h, 0x87, sz + sizeof (a_malloc_Header));
        free(h);
    #endif
}

a_static void *a_realloc(_aDef_  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(_aThr, 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);
#if 0
        a_malloc_Header *h1 = (a_malloc_Header *) realloc(h, sz1 + sizeof (a_malloc_Header));
            // これだと古い領域を利用させないためのmemsetができない.
        if (h1 == NULL)
            a_errExit("%s(%d): realloc_: out of memory: sz1=%d", a_fil, a_lin, (int) sz1);
#elif 1
        a_malloc_Header *h1 = h;
        if (sz0 > sz1)
            memset(((char *) h) + sz1 + sizeof (a_malloc_Header), 0x87, sz0 - sz1);
        if (sz0 < sz1) {
            h1 = malloc(sz1 + sizeof (a_malloc_Header));
            if (h1 == NULL)
                a_errExit("%s(%d): realloc_: out of memory: sz1=%d", a_fil, a_lin, (int) sz1);
            memcpy(h1, h, sz0 + sizeof (a_malloc_Header));
            memset(h, 0x87, sz0 + sizeof (a_malloc_Header));
            free(h);
            memset(((char *) h1) + sz0 + sizeof (a_malloc_Header), 0x87, sz1 - sz0);
        }
#endif
        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; // reallocしたときのlin/fil更新はしないことにした(かえって分かりにくくなるから).
        return h1 + 1;
    #endif
}

a_static void a_malloc_debugList(_aDef)
{
    #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
}

a_class(a_DbgObjInf) { intptr_t tblIdx, sig; };
a_class(a_DbgObjInfTbl) { intptr_t prv, nxt, lin; const char *nam, *fil; int64_t sn; };

a_static a_DbgObjInfTbl *a_DbgObjInfTbl_p;
a_static intptr_t a_DbgObjInfTbl_nxt = -1, a_DbgObjInfTbl_n, a_DbgObjInfTbl_n1;
a_static int64_t a_DbgObjInfTbl_sn = 0;

a_static void a_DbgObjInfTbl_extSiz()
{
    intptr_t i0 = a_DbgObjInfTbl_n1, i1, i;
    if (a_DbgObjInfTbl_n1 == 0) {
        a_DbgObjInfTbl_n1 = 256;
        a_DbgObjInfTbl_p = malloc(a_DbgObjInfTbl_n1 * sizeof (a_DbgObjInfTbl));
    } else {
        a_DbgObjInfTbl_n1 <<= 1;
        a_DbgObjInfTbl_p = realloc(a_DbgObjInfTbl_p, a_DbgObjInfTbl_n1 * sizeof (a_DbgObjInfTbl));
    }
    i1 = a_DbgObjInfTbl_n1;
    if (a_DbgObjInfTbl_p == NULL)
        a_errExit("a_DbgObjInfTbl_extSiz: out of memory: n1=%d", a_DbgObjInfTbl_n1);
    for (i = i0; i < i1; i++) {
        a_DbgObjInfTbl_p[i].nxt = i + 1;
        a_DbgObjInfTbl_p[i].lin = -1;
    }
    a_DbgObjInfTbl_p[i1 - 1].nxt = a_DbgObjInfTbl_nxt;
    a_DbgObjInfTbl_nxt = i0;
    if (a_DbgObjInfTbl_nxt == 0) {
        a_DbgObjInfTbl_nxt = 1;
        a_DbgObjInfTbl_p[0].prv = 0;
        a_DbgObjInfTbl_p[0].nxt = 0;
    }
}

a_static void a_DbgObjInf_ini(const char *a_fil, int a_lin, a_DbgObjInf *w, const char *nam)
{
    if (a_DbgObjInfTbl_nxt < 0) a_DbgObjInfTbl_extSiz();
    intptr_t i = a_DbgObjInfTbl_nxt;
    a_DbgObjInfTbl_nxt = a_DbgObjInfTbl_p[i].nxt;
    a_DbgObjInfTbl_p[i].lin = a_lin;
    a_DbgObjInfTbl_p[i].nam = nam;
    a_DbgObjInfTbl_p[i].fil = a_fil;
    a_DbgObjInfTbl_p[i].sn  = a_DbgObjInfTbl_sn++;
    a_DbgObjInfTbl_n++;
    if (w->sig == a_malloc_Sig && 0 < w->tblIdx && w->tblIdx < a_DbgObjInfTbl_n1 && a_DbgObjInfTbl_p[w->tblIdx].lin >= 0)
        a_errExit("%s(%d): a_DbgObjInf_ini: bad signature", a_fil, a_lin); // 偶然の一致ということもあり得るが...
    w->tblIdx = i;
    w->sig = a_malloc_Sig;
    intptr_t p = a_DbgObjInfTbl_p[0].prv;
    a_DbgObjInfTbl_p[p].nxt = i;
    a_DbgObjInfTbl_p[i].prv = p;
    a_DbgObjInfTbl_p[i].nxt = 0;
    a_DbgObjInfTbl_p[0].prv = i;
}

a_static void a_DbgObjInf_din(const char *a_fil, int a_lin, a_DbgObjInf *w)
{
    if (w->sig != a_malloc_Sig)
        a_errExit("%s(%d): a_DbgObjInf_din: bad signature", a_fil, a_lin);
    w->sig = 0;
    intptr_t i = w->tblIdx;
    a_DbgObjInfTbl_p[i].lin = -1;
    a_DbgObjInfTbl_n--;
    intptr_t p = a_DbgObjInfTbl_p[i].prv, n = a_DbgObjInfTbl_p[i].nxt;
    a_DbgObjInfTbl_p[p].nxt = n;
    a_DbgObjInfTbl_p[n].prv = p;
    a_DbgObjInfTbl_p[i].nxt = a_DbgObjInfTbl_nxt;
    a_DbgObjInfTbl_nxt = i;
}

a_static void a_DbgObjInfTbl_debugList(_aDef)
{
    #if (a_DbgLv >= 2)
        fprintf(stderr, "%s(%d): DbgObjInfTbl_debugList()\n", a_fil, a_lin);
        intptr_t i = a_DbgObjInfTbl_p[0].nxt;
        while (i > 0) {
            fprintf(stderr, "  [%s / %s(%d)]\n", a_DbgObjInfTbl_p[i].nam, a_DbgObjInfTbl_p[i].fil, (int) a_DbgObjInfTbl_p[i].lin);
            i = a_DbgObjInfTbl_p[i].nxt;
        }
    #endif
}

(99) 更新履歴

  • 2026.04.23(木) 初版

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2026-04-23 (木) 13:01:49 (54d)