C言語におけるsetjmpとlongjmp


C言語ではgoto文を使用して別の関数のlabelにジャンプすることはできません.しかし、setjmpとlongjmpの2つの関数が提供され、このタイプのブランチジャンプを完了します.後で、この2つの関数が異常を処理するのに非常に役立つことを示します.
setjmpとlongjmpの使用方法
1つの関数内でジャンプするにはgoto文を使用できます(どういうわけか中国人学生の目には悪名高い文章で、ほとんどの国内教材ではなるべく使わないように教えていますが、私から見れば、これは言語の問題ではなく、その言語を使っている人なので、Linuxコアの中にgoto文の応用が広がっているのを見てみましょう!)しかし、一つの関数内から別の関数のどこかにジャンプするとgotoは完成できないが、どうやって実現すればいいのだろうか.
かんすうかんジャンプげんり
我々が実現すべきGOTO文(自分で定義したもの)は、関数間で任意のジャンプを実現することができ、以下の例では、関数g()にf()関数のGOTO Label;ラベルが指す位置にジャンプできる文があるが、どのように実現すればよいのだろうか.
void f()
{
    //...
    Label:
    //...
}

void g()
{
    //...
    GOTO Label;
    //...
}


まず,このタイプのジャンプを実現することは,オペレーティングシステムにおけるタスク切替のコンテキスト切替と少し類似しており,Labelラベルの関数コンテキストを復元するだけでよいことを知っておく必要がある.関数のコンテキストは次のとおりです.
  • 関数スタックフレーム、主にスタックフレームポインタBPとスタックトップポインタSP
  • プログラムポインタPC、ここではLabel文へのアドレス
  • 他のレジスタは、システムに関連しており、x 86システムの下でAX/BX/CXなどのcallee-regsを保存する必要があります.

  • このように、Label:という文を実行すると、Labelのコンテキストを復元します.つまり、Labelにジャンプする機能を完了します.
    Linuxオペレーティングシステムのプロセス切り替えのソースコードを読んだことがあるなら、Linuxがプロセスのコンテキストをtask_に保存することをよく知っています.struct構造体では,切替時に直接回復する.ここでは、Labelの関数コンテキストをある構造体に保存することもできますが、GOTO Label文に実行すると、その構造体から関数コンテキストを復元します.
    これが関数間ジャンプの基本原理であり,C言語ではsetjmpとlongjmpがコンテキストの保存と切り替えを完了している.
    関数プロトタイプ
    #include <setjmp.h>
    int setjmp(jmp_buf env);
    

    setjmp関数の機能は、ここで関数のコンテキストをjmp_に保存することです.buf構造体では、longjmpがこの構造体から回復するために使用されます.
  • パラメータenvは、コンテキストを保存するjmp_である.buf構造体変数;
  • この関数を直接呼び出すと、戻り値は0になります.この関数がlongjmp呼び出しから返されると、戻り値はゼロではなく、longjmp関数によって提供されます.関数の戻り値に基づいて、setjmp関数呼び出しが初めて直接呼び出されたのか、それとも他の場所からスキップされたのかを知ることができます.
  • void longjmp(jmp_buf env, int val);
    

    longjmp関数の機能はjmp_からbuf構造体では、setjmp関数によって保存されたコンテキストが復元され、この関数は返されず、setjmp関数から返されます.
  • パラメータenvはsetjmp関数によって保存されたコンテキストです.
  • パラメータvalはlongjmp関数からsetjmp関数に渡される戻り値を表し、val値が0の場合setjmpは1を返し、そうでない場合valを返します.
  • longjmpは直接返さず、setjmp関数から返されます.longjmpが実行されると、プログラムはsetjmp関数から返されたばかりのようになります.

  • 単純なインスタンス
    以下に簡単な例を示します.まだ関数内ジャンプですが、この2つの関数の機能を説明するのに十分です.
    プログラムを実行した結果は次のとおりです.
    i = 0
    i = 2
    

    C言語異常処理
    Java、C#などのオブジェクト向け言語には例外処理のメカニズムがあり、以下のように典型的なJavaにおける例外処理のコードであり、2つの数が除算され、除算数が0で例外が投げ出されると、関数f()でその例外を取得して処理することができる.
    double divide(double to, double by) throws Bad {
        if(by == 0)
            throw new Bad ("Cannot / 0");
        return to / by;
    }
    
    void f() {
        try {
            divide(2, 0);
            //...   
        } catch (Bad e) {
            print(e.getMessage());
        }
        print("done");
    }
    

    C言語では同様の異常処理メカニズムはないが,setjmpとlongjmpを用いてこの機能の実現をシミュレートすることができ,これもこの2つの関数の重要な応用である.
    static jmp_buf env;
    
    double divide(double to, double by)
    {
        if(by == 0)
            longjmp(env, 1);
        return to / by;
    }
    
    void f() 
    {
        if (setjmp(env) == 0)
            divide(2, 0);
        else
            printf("Cannot / 0");
        printf("done");
    }
    

    複雑な場合、longjmpが伝達する戻り値に基づいて様々な異なる異常を判断し、区別する処理を行うことができ、コード構造は以下の通りである.
    switch(setjmp(env)):
        case 0:         //default
            //...
        case 1:         //exception 1
            //...
        case 2:         //exception 2
            //...
        //...
    

    C言語による異常処理については,この文章を参照して,より複雑な構造を紹介したが,setjmpとlongjmpの応用にほかならない.
    参考資料
  • http://en.wikipedia.org/wiki/Longjmp
  • www.cs.purdue.edu/homes/cs240/lectures/Lecture-19.pdf