Sat, 21 Dec 2002 20:13:03 JST / hina.di
powered by tds-1.3.0
<issei@issei.org>
C 言語で、込み入ったデータ構造を扱うプログラムを書いていると、ヒープメ モリの使用状況を追跡したくなります。自分で こんな コードを書いて、出力結果を解析する Perl スクリプトとあわせて使っていた のですが、もう少しマトモなツールが欲しくなったので探してみました。
まずは ccmalloc-0.3.2 を試してみようと思ったら、Linux, Solaris しか対応 していないので FreeBSD (ELF) で使えるように quick hack。 ついでに free(NULL) で怒られないように仕様変更。
スタックトレースがとれるのは便利ですね。どうやって実現しているのかとコー ドを読んでみたところ、次のようになっていました。
なるほどね。
こっちはメモリ管理を自動化する GC に関する Web Page。関連リンク集が充実 しているので、後で目を通しておこう。
杉村さんの日記から。渋谷で Mucha 展をやってるんですね。Mucha の作品は好きなので、時間 があるようなら、明日の午後にでも寄ってみようかしら。
あと国立科学博物館で開催さ れている ダイヤモンド展 も見に行きたいんですが、こちらは11/12までということで、まだ余裕がありま すね(とか言ってると、うっかり行く機会を逃すんだけど)。
女の子に面と向かって、
「先輩、女の子を一人ぐらい養ってみません?」
と言われて苦笑。やだよ(^^;
Cygwin の excpt.h から。
#ifndef _EXCPT_H #define _EXCPT_H /* FIXME: This will make some code compile. The programs will most likely crash when an exception is raised, but at least they will compile. */ #ifdef __GNUC__ #define __try #define __except(x) if (0) /* don't execute handler */ #define __finally #define _try __try #define _except __except #define _finally __finally #endif
Cygwin の gcc では Windows の構造化例外が使えないのか。メモリ保護違反が 発生する可能性があるメモリ領域にアクセスしたいんだけど、どうしよう? 構 造化例外が使えるなら、次のコードで OK だけど。
static alloc_state_t
validate(void *p, size_t size)
{
alloc_state_t state = XMA_OK;
__try {
/* 危険なメモリ領域へのアクセス */
}
__except (GetExceptionCode() == STATUS_ACCESS_VIOLATION) {
state = XMA_VIOLATED;
}
return state;
}
試してみたところ、Cygwin では構造化例外 STATUS_ACCESS_VIOLATION を SIGSEGV シグナルに変換して送出しているようなので、signal() 関数を使って 細工を試みる。
static jmp_buf jmp_env;
static void
sig_handler(int sig)
{
longjmp(jmp_env, 1);
}
static alloc_state_t
validate(void *p, size_t size)
{
static alloc_state_t state;
state = XMA_VIOLATED;
if (setjmp(jmp_env) == 0) {
signal(SIGSEGV, sig_handler);
signal(SIGBUS, sig_handler);
/* 危険なメモリ領域へのアクセス */
state = XMA_OK;
}
signal(SIGSEGV, SIG_DFL);
signal(SIGBUS, SIG_DFL);
return state;
}
とりあえず、これで動くみたい。本当は、シグナルハンドラから longjmp() を 呼び出すのはマズイんだけど、とりあえず /* XXX */ *1とコメントを付けて逃げておこう(ぉぃ
上記のように setjmp(), longjmp() を使うようにしたら、gcc が次の警告を出 力するようになる。
warning: variable `uval' may be clobbered by `longjmp' or `vfork'
uval というのは同じソースファイル中にある get_32bit() 関数内で定義してい る自動変数。警告は「longjmp() を呼び出すことで変数 `uval' の値がおかしく なるかもしれない」という意味だけど、なぜ?
gcc の info に答えを見つける。
* A nonvolatile automatic variable might be changed by a call to `longjmp'. These warnings as well are possible only in optimizing compilation.
The compiler sees only the calls to `setjmp'. It cannot know where `longjmp' will be called; in fact, a signal handler could call it at any point in the code. As a result, you may get a warning even when there is in fact no problem because `longjmp' cannot in fact be called at the place which would cause a problem.
なるほど。変数が `uval' がレジスタに割り当てられている場合、longjmp() 呼 び出しによって値が変わってしまうことがある、ということか。問題になりそう なコードの分かりやすい例は、こんなところかな。
int i = 0; // レジスタ %x に割り当て
if (setjmp(jmp_env) == 0) // %x を含むレジスタを jmp_env に退避
{
i++; // %x <- %x + 1
longjmp(jmp_env, 1); // %x を含むレジスタを jmp_env から復元
// して、ブロックから抜ける
}
printf("i = %d\n", i); // %x の値は 0 のまま
解決策は変数を volatile 宣言すること。これで変数の値をレジスタにキャッシュ せず、毎回メモリを参照するようになるので、longjmp() しても変数の値が破壊 されることがなくなる。
世間の流れに遅れまくってますが、Ruby 本 *1を購入してきて読んでいるところです。なかなか面白そう。
FreeBSD 5.0-CURRENT 環境を作ってみたものの、起動時に ldconfig を実行する ところで止まってしまう。C-\ で /etc/rc スクリプトの実行に割り込み ps コ マンドを実行すると、ldconfig のプロセス状態は D (割り込み不可能な短期間 の待ち状態) となっている。
同じ環境(5.0-CURRENT の userland インストール済み)で 4.1-RELEASE のカー ネルを実行させると問題なく動くので、明らかに 5.0-CURRENT カーネルの問題。 なんだろう?
しばらく FreeBSD の変更を追っていなかったら、いつの間にやら割り込みの処 理がスレッドベースになった模様。SMP 関係のコードも整理されたようだし、合 わせて読んでみるか。
p = NULL; // OK p は修飾子なしのポインタ *p = NULL; // OK *p は修飾子なしのポインタ **p = 0; // NG **p は「定数 int」
p = NULL; // OK p は修飾子なしのポインタ *p = NULL; // NG *p は「定数ポインタ」 **p = 0; // NG **p は「定数 int」
p = NULL; // NG p は「定数ポインタ」 *p = NULL; // OK p は修飾子なしのポインタ **p = 0; // OK p は修飾子なしの int
int *pn; int **ppn; p[0] = ppn; // OK p[0] は int へのポインタへのポインタ p[0] = &pn; // OK p[0] は int へのポインタへのポインタ *p = ppn; // OK *p は int へのポインタへのポインタ *(p[0]) = pn; // OK *(p[0]) は int へのポインタ *p[0] = pn; // OK こう書いても同じこと *p[0] = &n; // OK **p = pn; // OK p[0] と **p は同じアドレスを指している
int n; p++; // NG p は「定数ポインタ」 *p; // *p は int へのポインタの配列 (*p)++ // NG 配列をインクリメントすることは出来ない (*p)[0] = &n; // OK (*p)[0]は int へのポインタ
int *pn;
pn = (*p[0])(); // OK p[0] は int へのポインタを返す関数へのポインタ
// ポインタの指す関数を呼び出している
pn = (*(p[0]))();// OK 演算子の優先順位を明確にすると、こうなる
pn = (p[0])(); // OK ポインタ変数を介した関数呼び出しでは * を省略できる
pn = (**p[0])() // OK 逆に * を余分に付けても動く
extern int printf(const char *, ...);
printf("Hello world.\n"); // OK もちろん正しい
(printf)("Hello world.\n"); // OK これでも動く
(*printf)("Hello world.\n"); // OK * をつけても動く
(********printf)("Hello world.\n"); // OK ほんとに動くんだってば
extern int (*printf)(const char *, ...); // ポインタ変数として宣言してみる
printf("Hello world.\n"); // NG コンパイルは通るが、実行すると
// おかしくなるという、凶悪なバグ。
理解できれば方法はどうでも良いのですが、私は慣れないうちは変数名から始め て優先順位の高いものを順に追っていました *1。 語順の関係で、日本語より英語で考えたほうが分かりやすいので、以下そのよう に。面倒なので冠詞は適宜省略しています。
const int ci = 3, *pci; // OK const int ci = 3; const int *pci; int const xci = 3, *xpci; // OK 上と同じ pci = xpci; // OK
C 言語の「型」に関する文法はいかにも複雑ですが、C++ ではさらにテンプレー トやキャストのオーバーライドが加わり、もう何がなんだか。