或曰

……と、ある人は言った。

-- 2000年10月中旬 --

Sat, 21 Dec 2002 20:13:03 JST / hina.di
powered by tds-1.3.0
<issei@issei.org>



検索式: [検索方法]

表示件数: 表示形式: ソート:

[最新版] << == >> [一覧]

2000/10/12(Thu)

C 言語のメモリ管理を補助するプログラム

C 言語で、込み入ったデータ構造を扱うプログラムを書いていると、ヒープメ モリの使用状況を追跡したくなります。自分で こんな コードを書いて、出力結果を解析する Perl スクリプトとあわせて使っていた のですが、もう少しマトモなツールが欲しくなったので探してみました。

まずは ccmalloc-0.3.2 を試してみようと思ったら、Linux, Solaris しか対応 していないので FreeBSD (ELF) で使えるように quick hack。 ついでに free(NULL) で怒られないように仕様変更。

ccmalloc-0-3-2.freebsd.diff

スタックトレースがとれるのは便利ですね。どうやって実現しているのかとコー ドを読んでみたところ、次のようになっていました。

  1. gcc の組込み関数 __builtin_return_address() で関数のリターンアドレスを取得する。
  2. 実行ファイルから nm (1) を使ってシンボルをリストし、1 で得られたリターンアドレスと照らし合わせて、関数の呼び出し履歴を再構築する。

なるほどね。

こっちはメモリ管理を自動化する GC に関する Web Page。関連リンク集が充実 しているので、後で目を通しておこう。

Mucha展

杉村さんの日記から。渋谷で Mucha 展をやってるんですね。Mucha の作品は好きなので、時間 があるようなら、明日の午後にでも寄ってみようかしら。

あと国立科学博物館で開催さ れている ダイヤモンド展 も見に行きたいんですが、こちらは11/12までということで、まだ余裕がありま すね(とか言ってると、うっかり行く機会を逃すんだけど)。

今日の一言

女の子に面と向かって、

「先輩、女の子を一人ぐらい養ってみません?」

と言われて苦笑。やだよ(^^;


2000/10/13(Fri)

危険なメモリ領域へのアクセス

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とコメントを付けて逃げておこう(ぉぃ

_ warning: variable [...] may be clobbered by `longjmp' or `vfork'

上記のように 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() しても変数の値が破壊 されることがなくなる。


*1: XXX の由来は知りませんが、どうも「このコードは子供は見てはいけません」 という印らしいです。

Ruby 事始め

世間の流れに遅れまくってますが、Ruby 本 *1を購入してきて読んでいるところです。なかなか面白そう。


*1:「オブジェクト指向スクリプト言語Ruby」まつもとゆきひろ/石塚圭樹 共著 (アスキー出版局)


2000/10/17(Tue)

FreeBSD-current インストール

FreeBSD 5.0-CURRENT 環境を作ってみたものの、起動時に ldconfig を実行する ところで止まってしまう。C-\ で /etc/rc スクリプトの実行に割り込み ps コ マンドを実行すると、ldconfig のプロセス状態は D (割り込み不可能な短期間 の待ち状態) となっている。

同じ環境(5.0-CURRENT の userland インストール済み)で 4.1-RELEASE のカー ネルを実行させると問題なく動くので、明らかに 5.0-CURRENT カーネルの問題。 なんだろう?

_ 気になった変更点

しばらく FreeBSD の変更を追っていなかったら、いつの間にやら割り込みの処 理がスレッドベースになった模様。SMP 関係のコードも整理されたようだし、合 わせて読んでみるか。

Re: チャレンジチャレンジ

int const **p
「定数 int」へのポインタのポインタ
 p = NULL;       // OK	p は修飾子なしのポインタ
 *p = NULL;      // OK	*p は修飾子なしのポインタ
 **p = 0;        // NG	**p は「定数 int」
int const * const *p
「定数 int」への「定数ポインタ」へのポインタ
 p = NULL;       // OK	p は修飾子なしのポインタ
 *p = NULL;      // NG	*p は「定数ポインタ」
 **p = 0;        // NG	**p は「定数 int」
int ** const p
int へのポインタへの「定数ポインタ」
 p = NULL;       // NG	p は「定数ポインタ」
 *p = NULL;      // OK	p は修飾子なしのポインタ
 **p = 0;        // OK	p は修飾子なしの int
int **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 *(* const p)[]
int へのポインタの配列への「定数ポインタ」
 int n;

 p++;            // NG  p は「定数ポインタ」
 *p;             //     *p は int へのポインタの配列
 (*p)++          // NG  配列をインクリメントすることは出来ない

 (*p)[0] = &n;   // OK  (*p)[0]は int へのポインタ
int *(*p[])()
int へのポインタを返す関数へのポインタの配列
 int *pn;
 pn = (*p[0])();  // OK  p[0] は int へのポインタを返す関数へのポインタ
                  //     ポインタの指す関数を呼び出している
 pn = (*(p[0]))();// OK  演算子の優先順位を明確にすると、こうなる
 pn = (p[0])();   // OK  ポインタ変数を介した関数呼び出しでは * を省略できる
 pn = (**p[0])()  // OK  逆に * を余分に付けても動く
番外編 printf
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。 語順の関係で、日本語より英語で考えたほうが分かりやすいので、以下そのよう に。面倒なので冠詞は適宜省略しています。

int *p;
  1. 最初は p。
  2. p に隣接しているのは * なので "p is a pointer"
  3. *p に隣接しているのは int だから "p is a pointer to integer" となり、終わり。
int *p[];
  1. 最初は p。
  2. p に隣接しているのは左側の * と右側の [] があるが、[] の方が優先順位が高いので "p is an array"。
  3. 次に p[] に隣接しているのは * だから "p is an array of pointer"。
  4. 最後に *p[] に隣接しているのは int なので "p is an array of pointer to integer" となり、終わり。
int *(* const p)[];
  1. 最初は p。
  2. p に隣接しているのは左側の const だから "p is a constant"
  3. const p に隣接しているのは左側の * だから "p is a constant pointer"
  4. (* const p) に隣接しているのは左側の * と右側の [] があるが、[] の方が優先順位が高いので "p is a constant pointer to array"。
  5. (* const p)[] に隣接しているのは左側の * だけだから "p is a constant pointer to array of pointer"
  6. *(*const p)[] に隣接しているのは左側の int だけだから "p is a constant pointer to array of pointer to integer" でおしまい。
int *(*p[])();
  1. "p is an array of pointer" までは今までどおり。
  2. 次に (*p[]) に隣接しているのは左側の * と右側の () だが、() の方が優先順位が高いので "p is an array of pointer to function" となる。ポインタ の指す先が変数ではなく、関数となっています。
  3. (*p[])() に隣接しているのは左側の * と、さらにその左側の int。これは関数の戻り値の型を示しているので、まとめると "p is an array of pointer to function that returns a pointer to integer" となる。
落穂拾い
const char *p は char const *p と同じで、どちらも const は char を修飾し ます。前者を読み下すなら p is a pointer to char which is constant. ぐら いでしょうかね。
const int ci = 3, *pci;    // OK  const int ci = 3; const int *pci;
int const xci = 3, *xpci;  // OK  上と同じ

pci = xpci;                // OK

_ C++ では

C 言語の「型」に関する文法はいかにも複雑ですが、C++ ではさらにテンプレー トやキャストのオーバーライドが加わり、もう何がなんだか。


*1:もっとも、一読して理解できないほど複雑な型宣言が出てきたら、typedef する か、プログラムの構造を見直したほうが良いと思いますが。


以上、3日分です。

[最新版] [一覧] << == >>

Copyright © 2000 - 2000
Issei Suzuki <issei@issei.org>