10

abort()そのため、残念ながら特定のエラーを処理するために使用するライブラリ(私が作成したものではありません)があります。アプリケーション レベルでは、これらのエラーは回復可能であるため、ユーザーがクラッシュするのではなく、エラーを処理したいと考えています。したがって、次のようなコードを書くことになります。

static jmp_buf abort_buffer;
static void abort_handler(int) {
    longjmp(abort_buffer, 1); // perhaps siglongjmp if available..
}

int function(int x, int y) {

    struct sigaction new_sa;
    struct sigaction old_sa;

    sigemptyset(&new_sa.sa_mask);
    new_sa.sa_handler = abort_handler;
    sigaction(SIGABRT, &new_sa, &old_sa);

    if(setjmp(abort_buffer)) {
        sigaction(SIGABRT, &old_sa, 0);
        return -1
    }

    // attempt to do some work here
    int result = f(x, y); // may call abort!

    sigaction(SIGABRT, &old_sa, 0);
    return result;
}

非常にエレガントなコードではありません。このパターンはコードのいくつかの場所で繰り返さなければならないので、少し単純化し、再利用可能なオブジェクトにラップしたいと思います。私の最初の試みは、RAII を使用してシグナル ハンドラーのセットアップ/ティアダウンを処理することです (各関数で異なるエラー処理が必要なため、実行する必要があります)。だから私はこれを思いついた:

template <int N>
struct signal_guard {
    signal_guard(void (*f)(int)) {
        sigemptyset(&new_sa.sa_mask);
        new_sa.sa_handler = f;
        sigaction(N, &new_sa, &old_sa);
    }

    ~signal_guard() {
        sigaction(N, &old_sa, 0);
    }
private:
    struct sigaction new_sa;
    struct sigaction old_sa;
};


static jmp_buf abort_buffer;
static void abort_handler(int) {
    longjmp(abort_buffer, 1);
}

int function(int x, int y) {
    signal_guard<SIGABRT> sig_guard(abort_handler);

    if(setjmp(abort_buffer)) {
        return -1;
    }

    return f(x, y);
}

確かに の体はこの方functionはるかに単純で明快ですが、今朝、ある考えが浮かびました。これは動作することが保証されていますか? ここに私の考えがあります:

  1. setjmp/の呼び出し間で揮発性または変化する変数はありませんlongjmp
  2. longjmp同じスタック フレーム内の場所に移動し、setjmp通常returnは ing を実行するため、関数の終了ポイントでコンパイラが出力したクリーンアップ コードをコードが実行できるようにしています。
  3. 期待どおりに動作するようです。

しかし、これは未定義の動作である可能性が高いと感じています。皆さんはどう思いますか?

4

2 に答える 2

8

それfはサードパーティのライブラリ/アプリにあると思います。それ以外の場合は、中止を呼び出さないように修正できます。RAII がすべてのプラットフォーム/コンパイラで正しい結果を確実に生成する場合としない場合があることを考えると、いくつかのオプションがあります。

  • 定義する小さな共有オブジェクトを作成し、abortそれを LD_PRELOAD します。次に、シグナルハンドラーではなく、アボート時に何が起こるかを制御します。
  • サブプロセス内で実行fします。次に、戻りコードを確認し、失敗した場合は、更新された入力で再試行します。
  • RAII を使用する代わりに、function複数のコール ポイントからオリジナルを呼び出して、明示的にセットアップ/ティアダウンを手動で実行させます。その場合でも、コピーと貼り付けは不要です。
于 2011-03-22T16:17:28.117 に答える
2

私は実際にあなたのソリューションが好きで、ターゲットassert()が期待どおりに機能することを確認するために、テストハーネスで同様のものをコーディングしました。

このコードが未定義の動作を呼び出す理由がわかりません。C 標準はそれを祝福しているようabort()です。(注意: これは C99 の 7.14.1.1(5) です。残念ながら、C++ 標準で参照されているバージョンである C90 のコピーは持っていません)。

C++03 ではさらに制限が追加されています。プログラム内の別の (宛先) ポイントに制御を転送するスローされた例外によって自動オブジェクトが破棄される場合は、制御を転送するスロー ポイントで longjmp(jbuf, val) を呼び出します。同じ (宛先) ポイントの動作は未定義です。 「変数は揮発性ではなく、setjmp/longjmp の呼び出し間で変更されません」というステートメントには、自動 C++ オブジェクトのインスタンス化が含まれていると思います。(これはレガシーCライブラリだと思いますか?)。

また、POSIX非同期信号の安全性(またはその欠如)も問題ではありません-abort()プログラムの実行と同期してSIGABRTを生成します。

最大の懸念は、サード パーティ コードのグローバルな状態が壊れることabort()です。しかし、変数が変化しないことが正しければ、これは問題ではありません。

標準語をよりよく理解している人が私が間違っていることを証明できる場合は、啓発に感謝します。

于 2011-03-22T21:29:03.293 に答える