川合のプログラミング言語自作のためのテキスト第四版#001
(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_testAll a_malloc_testAll
#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_testAll(flg) a_DbgLv2(a_MallocMan_testAll(a_mallocMan, 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;
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_MallocMan) {
a_MallocSub sub[1];
unsigned char *canary;
intptr_t szCanary, no;
unsigned char unuseMark;
};
a_static a_MallocMan a_mallocMan[1];
a_static void a_MallocMan_ini(a_MallocMan *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_MallocMan_alc(a_MallocMan *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_MallocMan_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_MallocMan_chk(a_MallocMan *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_MallocMan_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 = Nod(v, j)->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情報I\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_MallocMan_chkErr(a_MallocMan *w, a_def intptr_t i, void *p, intptr_t sz, const char *f)
{
#if (a_DbgLv >= 2)
int chk = a_MallocMan_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_MallocMan_fre(a_MallocMan *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_MallocMan_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);
a_MallocSub_add(w->sub, HedFre, i, Nod(w->sub, HedFre)->nxt);
#endif
}
a_static void *a_MallocMan_rlc(a_MallocMan *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_MallocMan_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_MallocMan_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;
} else
return a_MallocMan_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_MallocMan_alc(NULL, sz)
#define a_free(p) a_MallocMan_fre(NULL, p, 0)
#define a_realloc(p, sz) a_MallocMan_rlc(NULL, p, sz, 0)
#define a_free1(p, sz) a_MallocMan_fre(NULL, p, 0)
#define a_realloc1(p, sz, sz0) a_MallocMan_rlc(NULL, p, sz, 0)
#endif
#if (a_DbgLv >= 2)
#define a_malloc(sz) a_MallocMan_alc(a_mallocMan, sz)
#define a_free(p) a_MallocMan_fre(a_mallocMan, p, -1)
#define a_realloc(p, sz) a_MallocMan_rlc(a_mallocMan, p, sz, -1)
#define a_free1(p, sz) a_MallocMan_fre(a_mallocMan, p, sz)
#define a_realloc1(p, sz, sz0) a_MallocMan_rlc(a_mallocMan, p, sz, sz0)
#endif
a_static void a_MallocMan_testAll(a_MallocMan *w, a_def int flg)
{
#if (a_DbgLv >= 2)
fprintf(stderr, "%s(%d): a_MallocMan_testAll: 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_MallocMan_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_MallocMan_alc(a_mallocMan, sz)
#define a_free(p) a_MallocMan_fre(a_mallocMan, p, -1)
#define a_realloc(p, sz) a_MallocMan_rlc(a_mallocMan, p, sz, -1)
#define a_free1(p, sz) a_MallocMan_fre(a_mallocMan, p, sz)
#define a_realloc1(p, sz, sz0) a_MallocMan_rlc(a_mallocMan, 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」のようなメッセージ内容は指定できないのですが、代わりに行番号が出るので、むしろめんどくさいことを考えなくてよくて重宝します。
こめんと欄