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します。