メールからc Ruby-異常に入り始める


この间は本当に时間がなくて、9月上旬は更に时間がなくて、头が大きいです.
おととい面接のテーマを書いたときにsetjmpとlongjmpの2つの方法に出会いました.
そこでR ubyの異常処理がどのように実現されたかを考えて、ついでに研究した.
他のRuby関連の実現は今本当に書く時間がありません.しかし、私はRが好きなので、普通の好きではありません.
 
===============================
 
兵馬が動かず,穀物や草が先行する.
 
raiseがどのように実現されたか見てみたいのですが、irbでraiseを叩いた後、gdbでどの方法でブレークポイントを打つべきか分かりません.
いきなり出撃するのは盲目に違いない.フィリピンの特警が香港人観光客の人質を救うように、少しも考えがない.
じゃ、私はやはり予習をして、コードをめくってraiseの対応するC方法を探します.
 
 
evalを読むcのコードは、次のような方法で発見されました.
 
 
void
Init_eval(void)
{
    rb_define_virtual_variable("$@", errat_getter, errat_setter);
    rb_define_virtual_variable("$!", errinfo_getter, 0);

    rb_define_global_function("raise", rb_f_raise, -1);
    rb_define_global_function("fail", rb_f_raise, -1);
/*
    ..........
*/
}
 
ここでraiseという文字列に対応する方法はrb_ですf_raise,
 
 
static VALUE
rb_f_raise(int argc, VALUE *argv)
{
    VALUE err;
    if (argc == 0) {
	err = get_errinfo();
	if (!NIL_P(err)) {
	    argc = 1;
	    argv = &err;
	}
    }
    rb_raise_jump(rb_make_exception(argc, argv));
    return Qnil;		/* not reached */
}
 
他の方法の下位実装も見ることができます.例えば、
 
  • $@は、エラー情報を取得するコード行であり、errat_を介してgetter実装
  • $!エラーメッセージを取得し、errinfo_を介してgetter実装
  • Ruby異常メカニズムを理解するにはrb_f_raiseは綿密な分析を行った.
    次のようなシーンを設計します.
     
  • irbにおいてraiseにより異常
  • を放出する
  • gdbでrb_をf_raise設定ブレークポイント
  • irbのraiseコード
  • を実行する
  • gdbでstepがrb_に入るf_raiseとその中で、btを通じてその呼び出しスタックと最下層の実現方式を観察する.

  •  
    次のように操作します.
     
    IRbは
    >> raise ArgumentError,"Debug Ruby from main.c"
    ArgumentError: Debug Ruby from main.c
    from (irb):15
    from/usr/local/ruby-1.9.1/bin/irb:12:in `
    '
     
    gdbは
    (gdb) b rb_f_raise
    Breakpoint 10 at 0x1000289dc: file eval.c, line 467.
    (gdb) c
    Continuing.
     
     
    IRbは
    >> raise ArgumentError,"Debug Ruby from main.c"
     
     
    gdbは
    Breakpoint 10, rb_f_raise (argc=2, argv=0x100400298) at eval.c:467
    467 if (argc == 0) {
     
    この時私たちはもうrbを切った.f_raise、次はstepがその中に入って、1つの言語がどのように異常に対する処理を設計するかを見てみましょう.
     
    私は一歩一歩のコードを貼るのではなく、一番奥まで歩いて、以下の2つの関数に出会ったとき、ルビーの異常処理がどのように実現されたのかを大体知ることができると思います.
     
     
    int	_setjmp(jmp_buf);
    void	_longjmp(jmp_buf, int);
     
    C++とJavaの実現に深く入り込んだ学生もこの2つの関数を見たことがあるに違いない.彼らの役割は:
     
  • setjmp:現在のスタック情報を収集しjmp_に保存bufでは、戻り値はlongjmpであり、longjmpから返されない場合は0を返す.
  • longjmp:jmp_にジャンプbufが設定位置は、intをsetjmpに戻す.

  • 以下にsetjmpとlongjmpの解釈もある.
     
    私はsetjmpを使うことについて面接の問題を出したことがあります.hにおける方法は異常処理を実現し、ここでは簡単な応用例として以下のようにすることができる.
     
     
    #include <setjmp.h>
    jmp_buf jb;
    int ret = setjmp(jb);
    switch(ret)
    {
    case 0:/*ok*/;break;
    case 1:exc1_handler();break;
    case 2:exc2_handler();break;
    default:default_handler();
    }
     
    if(a==b){/*do ok*/}
    else{/*raise exception*/longjmp(jb,1)}

     
    この2つの関数を知って、私达は少し楽しくなることができて、もとは多くの言语はすべてそれらを使って异常な処理を実现して、ただ机能の豊富さの程度は异なって、しかし上のこれらを理解することを通じて(通って)、少なくとも私达は1つの大まかな考え方を知って、私达の自分の言语に多くの启発を设けました.
     
    話題はR ubyのデバッグに戻り、longjmpに切断されたとき、btコマンドを使用して呼び出しスタックを確認したところ、Rubyレベルでraise文を実行するときに、どのキー関数を呼び出すことで異常処理機能を実現しているかが明らかになった.
     
    gdbは
    #0 rb_sourcefile () at vm.c:754
    #1 0x00000001000274cd in rb_longjmp (tag=6, mesg=4304442240) at eval.c:358
    #2 0x00000001000277e8 in rb_raise_jump (mesg=) at eval.c:530
    #3 0x0000000100028a07 in rb_f_raise (argc=, argv=) at eval.c:474
     
     
     
    raise全体の流れの差は多くありません.このようにして、いくつかの関数をいくつか分析しています.
     
    rb_make_Exceptionはrb_raise_jumpの時に呼び出され、
    彼の役割はrbに異常なオブジェクトを生成することです.
    このexceptionの生成過程を見てみましょう.
     
     
    VALUE
    rb_make_exception(int argc, VALUE *argv)
    {
        VALUE mesg;
        ID exception;
        int n;
    
        mesg = Qnil;
        switch (argc) {
          case 0:
    	break;
          case 1:
    	if (NIL_P(argv[0]))
    	    break;
    	mesg = rb_check_string_type(argv[0]);
    	if (!NIL_P(mesg)) {
    	    mesg = rb_exc_new3(rb_eRuntimeError, mesg);
    	    break;
    	}
    	n = 0;
    	goto exception_call;
    
          case 2:
          case 3:
    	n = 1;
          exception_call:
    	CONST_ID(exception, "exception");
    	if (!rb_respond_to(argv[0], exception)) {
    	    rb_raise(rb_eTypeError, "exception class/object expected");
    	}
    	mesg = rb_funcall(argv[0], exception, n, argv[1]);
    	break;
          default:
    	rb_raise(rb_eArgError, "wrong number of arguments");
    	break;
        }
        if (argc > 0) {
    	if (!rb_obj_is_kind_of(mesg, rb_eException))
    	    rb_raise(rb_eTypeError, "exception object expected");
    	if (argc > 2)
    	    set_backtrace(mesg, argv[2]);
        }
    
        return mesg;
    }

     
    こんなにたくさんのコードを羅列するのは确かに耻ずかしいです.各行に説明する必要はありませんが、一部のコードの断片だけを取ると、分からない人がいるのではないかと心配しています.
     
     
    mesg = rb_check_string_type(argv[0]);
    ここではエラーメッセージargvをStringに変換します
     
     
    mesg = rb_exc_new3(rb_eRuntimeError, mesg);
    ここではmesgを異常なオブジェクトにしますが、それ以前はStringにすぎません.
    メッセージルビータイプを変更するときに強制変換を加える必要がないのは、CレベルですべてのR ubyオブジェクトがVALUEであるためです.
     
    rb_exc_新w 3はrb_を通りますfuncall(etype, rb_intern("new"), 1, str);実現したのです
    またrb_を見ましたinternメソッドは、rubyのメソッド名をidに変換し、そのidに対応するこの関数のCの実装を見つけてCの実装を実行する.これはCの中でRuby関数の1つの共通の構想を使うのです.
     
     
    CONST_ID(exception, "exception");
    このマクロの定義は次のとおりです.
     
     
    #define CONST_ID_CACHE(result, str)			\
        {							\
    	static ID rb_intern_id_cache;			\
    	if (!rb_intern_id_cache)			\
    	    rb_intern_id_cache = rb_intern2(str, strlen(str));	\
    	result rb_intern_id_cache;			\
        }
    #define CONST_ID(var, str) \
        do CONST_ID_CACHE(var =, str) while (0)

    ここでstaticタイプのrb_intern_id_CacheはexceptionのC実装を保存するために使用され,このメソッドに再アクセスする際にrb_を使用しなくてもよい.intern 2は取りに行きました.
     
     
    setjmpとlongjmpの解釈を貼ってください.
     
    setjmp longjmpは
    setjmp|longjmp
    goto文は、刺激的なabort()およびexit()に比べて、異常を処理するより実行可能なスキームのように見えます.残念なことに、gotoはローカルです.それは関数の内部のラベルにジャンプするだけで、制御権をプログラムの任意の場所に移動することはできません(もちろん、すべてのコードがmain体にない限り).
    この制限を解決するために、C関数ライブラリはsetjmp()とlongjmp()関数を提供し、それらはそれぞれ非局所符号とgotoの役割を果たす.ヘッダファイルこれらの関数と同時に必要なjmp_を明らかにした.bufデータ型.
    原理は非常に簡単です.
      1.setjmp(j)は「jump」点を設定し、jmp_を正しいプログラムコンテキストで埋め込むbufオブジェクトj.このコンテキストには、プログラム格納位置、スタック、およびフレームワークポインタ、その他の重要なレジスタおよびメモリデータが含まれます.jumpのコンテキストを初期化すると、setjmp()は0値を返します.
      2. 後でlongjmp(j,r)を呼び出す効果は、jによって記述されるコンテキスト(すなわち、jが設定されたsetjmp()に非局所的なgotoまたは「ロングジャンプ」である.ロングジャンプのターゲットとして呼び出されると、setjmp()はrまたは1(rが0に設定されている場合)を返します.(setjmp()はこの場合0を返すことはできません.)
    2つの戻り値があることで、setjmp()がどのように使用されているかを知ることができます.jを設定すると、setjmp()は所望のように実行されます.しかし、長いジャンプのターゲットとしてsetjmp()は、そのコンテキストを外部から「呼び覚ます」.longjmp()で例外を終了し、setjmp()で対応する例外ハンドラをマークできます.