LISP ABORT

nptのドキュメントです。
参照元:ANSI Common Lisp npt
前へ:関数の登録
次へ:脱出の操作

5.1 LISP ABORT

LISP ABORTとは、システムエラーによる強制終了のことです。
C言語のexit関数を終了コード1で呼び出し、プロセスを終了させます。

もし手動でabortを起こしたい場合は、次の関数を実行します。

lisp_abort();

実行結果は次のようになります。

$ ./a.out


**************
  LISP ABORT
**************
$ echo $?
1
$

実行例のように、LISP ABORTという文言が出力された場合は、 プロセスが強制終了されたことを意味します。
確認している通り、そのときの終了コードは1です。

終了時にメッセージを出力したい場合は下記の関数を使用します。

void lisp_abortf(const char *fmt, ...);
void lisp_abort8(const void *fmt, ...);
void lisp_abort16(const void *fmt, ...);
void lisp_abort32(const void *fmt, ...);

名前に8, 16, 32が付いている関数は、lisp_string8_関数でメッセージを作成します。
formatを経由するため、Common Lispが動作している必要があります。

lisp_abortfは、引数にprintfの書式を受け取ります。
formatを経由しないため、Common Lispが動作している必要はありません。
ただしprintfの引数にLispのオブジェクトを指定することはできません (できたとしても%pくらいだと思います)。

5.2 ハンドラーの設定

lisp_abortの挙動はハンドラーを登録することで変更できます。
例えば、下記のハンドラーを設定してみます。

void test_handler(void)
{
    printf("Hello Handler.\n");
}

ハンドラーの登録はlisp_set_abort_handler関数にて行います。
例えば次のようになります。

lisp_set_abort_handler(test_handler);
lisp_abort();

実行結果を下記に示します。

$ ./a.out
Hello Handler.


**************
  LISP ABORT
**************
$

ハンドラーが起動されたあと、LISP ABORTが発生して プロセスが強制終了したことが分かります。
ハンドラーを登録したとしても、 そのまま終了した場合はLISP ABORTが実行されます。

5.3 LISP ABORTの捕捉

LISP ABORTは、setjmpを使うことでプロセスを終了させることなく 処理を継続させることができます。
下記の命令を用いることで、捕捉と継続ができます。

lisp_set_abort_setjmp_handlerは、補足用のハンドラーを設定します。
Lisp_abort_BeginLisp_abort_Endは、捕捉処理を囲むためのマクロです。

LISP ABORTを補足する例を下記に示します。

int main(void)
{
    lisp_set_abort_setjmp_handler();
    Lisp_abort_Begin {
        printf("Start\n");
        lisp_abort();
        printf("End\n");
    }
    Lisp_abort_End;

    printf("Return\n");

    return 0;
}

実行結果は下記のとおりです。

$ ./a.out
Start
Return
$

実行結果ではStartReturnが表示されており、 Endが表示されていないのがわかります。
lisp_abort関数によりabortがが発生して、 処理がLisp_abort_Endまでジャンプしたためです。
上記の方法だと、処理が普通に終わったものなのか、 あるいはLISP ABORTが発生したものなのか区別がつきません。
そこで、次のように変更します。

void main_call(void)
{
    printf("Start\n");
    lisp_abort();
    printf("End\n");
}

int main(void)
{
    int finish;

    lisp_set_abort_setjmp_handler();
    finish = 0;
    Lisp_abort_Begin {
        main_call();
        finish = 1;
    }
    Lisp_abort_End;

    if (finish == 0)
        printf("Lisp Abort\n");

    return 0;
}

変数finishを用意し、Lisp_abort_Beginの最後で 値が変更されていたら正常終了したとみなします。
実行結果を下記に示します。

$ ./a.out
Start
Lisp Abort
$

5.4 C++での使用

LISP ABORTのハンドラーはsetjmpが使われますが、 C++でコンパイルした場合はtry/catchに変更されます。
変更する理由はsetjmpであればデストラクタが起動しないためです。
例として下記の文をC++で実行してみます。

class destruct
{
public:
    destruct() { printf("Constructor\n"); };
    ~destruct() { printf("Destructor\n"); };
};

void main_call(void)
{
    destruct x;

    printf("Start\n");
    lisp_abort();
    printf("End\n");
}

int main(void)
{
    int finish;

    lisp_set_abort_setjmp_handler();
    finish = 0;
    Lisp_abort_Begin {
        main_call();
        finish = 1;
    }
    Lisp_abort_End;

    if (finish == 0)
        printf("Lisp Abort\n");

    return 0;
}

実行例は下記のとおりです。

$ ./a.out
Constructor
Start
Destructor
Lisp Abort
$

この例ではsetjmpではなくtry/catchが使われているため、 デストラクタが起動しているのが確認できます。
このような挙動を好ましく思わない場合は、 コンパイル時にLISP_ABORT_SETJMPをdefineすることで、 C++コンパイルでもsetjmpを使用することができます。
上記の例文をsetjmpで実行した結果を下記に示します。

$ ./a.out
Constructor
Start
Lisp Abort
$

lisp_abort関数が実行されてると そのままLisp_abort_Endへと遷移しているため、 デストラクタが無視されているのがわかります。