acl4のページ0002
(1)
#if (a_Version >= 1)
#define DoublyLinkList a_DoublyLinkList
#define DoublyLinkList_ini a_DoublyLinkList_ini
#define DoublyLinkList_insTail a_DoublyLinkList_insTail
#define DoublyLinkList_insHead a_DoublyLinkList_insHead
#define DoublyLinkList_rmv a_DoublyLinkList_rmv
#endif
a_class(a_DoublyLinkList) { a_DoublyLinkList *prv, *nxt; };
a_static void a_DoublyLinkList_ini(a_DoublyLinkList *w) { w->prv = w->nxt = w; }
a_static void a_DoublyLinkList_insTail(a_DoublyLinkList *w, a_DoublyLinkList *tgt)
{
a_DoublyLinkList *lst = w->prv;
lst->nxt = tgt;
tgt->prv = lst;
tgt->nxt = w;
w ->prv = tgt;
}
a_static void a_DoublyLinkList_insHead(a_DoublyLinkList *w, a_DoublyLinkList *tgt)
{
a_DoublyLinkList *hed = w->nxt;
w ->nxt = tgt;
tgt->prv = w;
tgt->nxt = hed;
hed->prv = tgt;
}
a_static void a_DoublyLinkList_rmv(a_DoublyLinkList *tgt)
{
a_DoublyLinkList *prv = tgt->prv;
a_DoublyLinkList *nxt = tgt->nxt;
prv->nxt = nxt;
nxt->prv = prv;
}
#if (a_Version >= 1)
#define malloc_ a_malloc
#define free_ a_free
#define realloc_ a_realloc
#endif
a_class(a_malloc_Header) {
a_DoublyLinkList lnk[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->lnk->prv == NULL) {
a_DoublyLinkList_ini(a_malloc_hed0->lnk);
a_malloc_hed0->sz = -1;
a_malloc_hed0->sig = a_malloc_Sig;
a_malloc_hed0->lin = 0;
}
a_DoublyLinkList_insTail(a_malloc_hed0->lnk, h->lnk);
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_DoublyLinkList_rmv(h->lnk);
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_DoublyLinkList *prv = h->lnk->prv;
a_DoublyLinkList *nxt = h->lnk->nxt;
prv->nxt = nxt;
nxt->prv = prv;
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);
prv->nxt = h1->lnk;
nxt->prv = h1->lnk;
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->lnk->prv != NULL) {
a_DoublyLinkList *t = a_malloc_hed0->lnk->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->lnk->prv != NULL) {
a_DoublyLinkList *t = a_malloc_hed0->lnk->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), h->sz, h->fil, h->lin);
t = t->nxt;
}
}
#endif
}
(2) DoublyLinkList
- 私の大好きな双方向リストを簡単に扱うためのライブラリ関数群です。
- まず a_class(a_DoublyLinkList) { a_DoublyLinkList *prv, *nxt; }; で構造体を宣言しています。
- 最初なので、これがどう展開されるかを説明します。
typedef struct a_DoublyLinkList ## _ a_DoublyLinkList;
struct a_DoublyLinkList ## _ { a_DoublyLinkList *prv, *nxt; };
- となったあと、
typedef struct a_DoublyLinkList_ a_DoublyLinkList;
struct a_DoublyLinkList_ { a_DoublyLinkList *prv, *nxt; };
- となるのです。これで自前でtypedefしなくてもa_DoublyLinkListをstructなしで書けるようになるわけです。
- 私はよく使う語に関しては短く書くのが好きです。
- prv → previous
- nxt → next
- ini → init
- ins → insert
- rmv → remove
- tgt → target
- またクラスのthisポインタのことをwと略します。これは私がオブジェクトのことをworking-area(作業領域)と呼んでいたころの名残りです。
- こんな簡単な処理くらい、関数化しないで手書きしてもいいですし実際にそうすることもありますが、たまに間違えてデバッグで苦労するので、関数で処理することが多いです。まあたぶんほとんどインライン展開されていて、手書きと同じ速度になっているような気はします。
(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) 更新履歴