setjmpを呼びたすための関数を作ってはならない!

以前に「C言語でJavaと同等程度の例外処理をやってみる - ほんまの走り書き技術メモ」でC言語でも例外処理っぽいこと出来るんやぞってのを書きました。今日その辺をいじっていて気付いたことがあったのでメモ。

jmp_bufとはなんぞや?

以前にも説明したが、jmp_bufは呼ばれた時の実行コンテキスト(プログラムカウンタ(CP)を含む汎用レジスタの値など)が格納される。しかし、これはスタック構造とかにはなってわけではないので、例外処理をするたびに新たに宣言しなくてはならない。

typedef struct
{
   unsigned int pc;
   unsigned int sp;
   unsigned int bp;
   unsigned int si;
   unsigned int di;
} jmp_buf[1];

とか

typedef _JBTYPE jmp_buf[_JBLEN];

のようになっている。(使う環境による)

じゃあスタック構造にすればいいやん!

jmp_bufをスタックで扱う構造体と、その操作関数郡を作ってみた。
(ちなみに線形リストの構造になっている。)

xl_control_setはsetjmpを呼び出し、スタックに貯める。(つもり)
xl_control_jmpはスタックから取り出し、longjmpを呼び出す。(つもり)

struct xl_control {
	int             size;
	xl_control_node *top;
	xl_control_node *bottom;
};

struct xl_control_node {
	jmp_buf         env;
	xl_control_node *next;
	xl_control_node *pre;
};

int xl_control_set() {
	xl_control_node *node = (xl_control_node *)malloc(sizeof(xl_control_node));
	int event;

	memcpy(node->env, env, sizeof(jmp_buf));
	node->next = NULL;
	node->pre  = NULL;

	if ((event = setjmp(node->eve)) != 0) {
		return 0;
	}
	if (!jmp_control->bottom) {
		jmp_control->top = node;
	} else {
		node->pre = jmp_control->bottom;
		jmp_control->bottom->next = node;
	}
	jmp_control->bottom = node;
	jmp_control->size++;

	return event;
}

void xl_control_jmp(int event) {
	xl_control_node *node = jmp_control->bottom;
	jmp_buf env;

	memcpy(env, node->env, sizeof(jmp_buf));
	if (!node->pre) {
		jmp_control->top = NULL;
	} else {
		node->pre->next = NULL;
	}
	jmp_control->bottom = node->pre;
	jmp_control->size--;
	free(node);

	longjmp(env, event);
}

(一部のみ抜粋)

これで以前のようなプログラムを動かすと?

#include <stdio.h>
#include "xl_control.h"

int my_div(int a, int b) { /* throws XL_CONTROL_EVENT_ZERO_DIV */
	if (a == 0) {
		xl_control_jmp(XL_CONTROL_EVENT_ZERO_DIV);
	}
	if (b == 0) {
		xl_control_jmp(XL_CONTROL_EVENT_THROW);
	}

	return a / b;
}

int main(int argc, char **argv) {
	int x, y, ans, e;

	xl_control_init();

	if (argc != 3) {
		fprintf(stderr, "Usage: ./longjmp X Y\n");
		return 1;
	}
	x = atoi(argv[1]);
	y = atoi(argv[2]);

	if ((e = xl_control_set()) == 0) {
		ans = my_div(x, y);
		printf("%d / %d = %d\n", x, y, ans);
	} else {
		fprintf(stderr, "Zero div exception!\n");
	} else { /* catch (Exception e) */
		fprintf(stderr, "Exception!\n");
	}

	xl_control_free();

	return 0;
}

あれれ。。。

$ ./test.exe 10 0
10 / 0 = 5

setjmpを呼び出すための関数を作ってはならない

デバッグしたところ、longjmpは働いており、xl_control_setのsetjmpのif文内まで入っていることは確認できたが・・・
xl_control_setの戻り先がmy_divの後になってました。
(多分、例外処理開始時のxl_control_setで、setjmpを呼び出しスタックポインタを含むレジスタ情報を保持するが、その後xl_control_setから抜けるためスタックのデータが失われるためと考えられる。)←自分の勝手な予想なので間違っていたら教えてください。

よって、setjmpを呼び出すための関数を作ってはならないことが判明した。(inline関数でも同じ)

じゃぁマクロにすればいいやん

ってことでマクロで解決することができたので公開です。
XL_TRYとXL_THROWっていうマクロを作りました。
XL_TRYは制御構造のように使えます。
もうC言語じゃないような気が・・(汗

実装例

こんな感じで使えます。

#include <stdio.h>
#include "xl_control.h"

int my_div(int a, int b) { /* throw ZERO_DIV_EXCEPTION */
	if (a == 0) {
		XL_THROW(XL_CONTROL_EVENT_THROW);
	}
	if (b == 0) {
		XL_THROW(XL_CONTROL_EVENT_THROW);
	}

	return a / b;
}

int main(int argc, char **argv) {
	int x, y, ans, e;

	xl_control_init();

	if (argc != 3) {
		fprintf(stderr, "Usage: ./longjmp X Y\n");
		return 1;
	}
	x = atoi(argv[1]);
	y = atoi(argv[2]);

	XL_TRY(e) {
		ans = my_div(x, y);
		printf("%d / %d = %d\n", x, y, ans);
	} else { /* catch (Exception e) */
		printf("Exception!\n");
	}

	xl_control_free();

	return 0;
}
実行結果
$ ./test.exe 10 3
10 / 3 = 3

$ ./test.exe 10 0
Zero div exception!

$ ./test.exe 0 3
Exception!

おk!!

ソース

xl_control.h
#ifndef XL_CONTROL_H
#define XL_CONTROL_H

#include <setjmp.h>
#include <stdlib.h>
#include <string.h>

#define XL_TRY(e)                             \
	do {                                  \
		jmp_buf env;                  \
		if ((e = setjmp(env)) == 0) { \
			xl_control_set(env);  \
		}                             \
	} while (0);                          \
	if (e == XL_CONTROL_EVENT_TRY)
#define XL_THROW(e) xl_control_jmp(e)

enum {
	XL_CONTROL_EVENT_TRY = 0,
	XL_CONTROL_EVENT_EXCEPTION,
	XL_CONTROL_EVENT_ZERO_DIV,
};

typedef struct xl_control xl_control;
typedef struct xl_control_node xl_control_node;

struct xl_control {
	int             size;
	xl_control_node *top;
	xl_control_node *bottom;
};

struct xl_control_node {
	jmp_buf         env;
	xl_control_node *next;
	xl_control_node *pre;
};

void xl_control_init();
void xl_control_free();
void xl_control_set(jmp_buf env);
void xl_control_jmp(int event);
xl_control *xl_control_new();

#endif
xl_control.c
#include "xl_control.h"

static xl_control *jmp_control = NULL;
jmp_buf share_jmp_buf;

void xl_control_init() {
	if (!jmp_control) {
		jmp_control = xl_control_new();
	}
}

xl_control *xl_control_new() {
	xl_control *ctrl = (xl_control *)malloc(sizeof(xl_control));

	ctrl->size   = 0;
	ctrl->top    = NULL;
	ctrl->bottom = NULL;

	return ctrl;
}

void xl_control_free() {
	xl_control_node *node, *next;

	for (node = jmp_control->top; node != NULL; node = next) {
		next = node->next;
		free(node);
	}
	free(jmp_control);
	jmp_control = NULL;
}

void xl_control_set(jmp_buf env) {
	xl_control_node *node = (xl_control_node *)malloc(sizeof(xl_control_node));

	memcpy(node->env, env, sizeof(jmp_buf));
	node->next = NULL;
	node->pre  = NULL;

	if (!jmp_control->bottom) {
		jmp_control->top = node;
	} else {
		node->pre = jmp_control->bottom;
		jmp_control->bottom->next = node;
	}
	jmp_control->bottom = node;
	jmp_control->size++;
}

void xl_control_jmp(int event) {
	xl_control_node *node = jmp_control->bottom;
	jmp_buf env;

	memcpy(env, node->env, sizeof(jmp_buf));
	if (!node->pre) {
		jmp_control->top = NULL;
	} else {
		node->pre->next = NULL;
	}
	jmp_control->bottom = node->pre;
	jmp_control->size--;
	free(node);

	longjmp(env, event);
}

ちなみに

まだ、例外処理が入れ子になった場合に、外の例外まで飛んで行けない。。
またできたらうpします。

追記

更に外に投げるんだったらelse文でその他の例外をキャッチしてるんだから、そこで

} else {
	XL_THROW(e);
}

とすればいいだけか。。。
Javaとかの例外処理と一緒だね。(自動では投げられないけど。)
あっもちろん初めから計算済みさーwww