Longjmp и RAII - PullRequest
       29

Longjmp и RAII

10 голосов
/ 22 марта 2011

Итак, у меня есть библиотека (не написанная мной), которая, к сожалению, использует 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, обычно, поэтому я позволяю коду выполнить код очистки, который компилятор выдал на выходеТочки функции.
  3. Кажется, что работает как ожидалось.

Но я все еще чувствую, что это, вероятно, неопределенное поведение.Что вы, ребята, думаете?

Ответы [ 2 ]

8 голосов
/ 22 марта 2011

Я предполагаю, что f находится в сторонней библиотеке / приложении, потому что в противном случае вы могли бы просто исправить это, чтобы не вызывать abort.Учитывая это и то, что RAII может или не может надежно давать правильные результаты на всех платформах / компиляторах, у вас есть несколько вариантов.

  • Создать крошечный общий объект, который определяет abort и LD_PRELOAD его.Затем вы контролируете, что происходит при прерывании, а НЕ в обработчике сигналов.
  • Запуск f в подпроцессе.Затем вы просто проверяете код возврата и, если он не удался, попробуйте снова с обновленными вводами.
  • Вместо использования RAII, просто вызовите ваш исходный function из нескольких точек вызова и позвольте ему вручную выполнить настройку / разбор,В этом случае он по-прежнему исключает копирование-вставку.
2 голосов
/ 23 марта 2011

Мне действительно нравится ваше решение, и я запрограммировал что-то подобное в тестовых цепях, чтобы проверить, что целевая функция assert() s, как и ожидалось.

Я не вижу причин для того, чтобы этот код вызывал неопределенное поведение,Стандарт C, кажется, благословляет это: обработчики, следующие из abort(), освобождены от ограничения на вызов функций библиотеки из обработчика.(Предостережение: это 7.14.1.1 (5) C99 - к сожалению, у меня нет копии C90, версии, на которую ссылается Стандарт C ++).

C ++ 03 добавляет еще одно ограничение: Если какие-либо автоматические объекты будут уничтожены с помощью брошенного исключения, передающего управление в другую (целевую) точку в программе, то вызов longjmp (jbuf, val) в точке выброса, которая передает управление в ту же (целевую) точкуимеет неопределенное поведение. Я полагаю, что ваше утверждение о том, что «переменные не являются изменчивыми или изменяются между вызовами setjmp / longjmp», включает создание экземпляров любых автоматических объектов C ++.(Полагаю, это какая-то устаревшая библиотека C?).

Также не проблема безопасности асинхронных сигналов POSIX (или ее отсутствия) - abort() генерирует свой SIGABRT синхронно с выполнением программы.

Наибольшее беспокойство вызывает искажение глобального состояния стороннего кода: вряд ли автор приложит усилия, чтобы согласовать состояние до abort().Но если вы правы в том, что переменные не меняются, тогда это не проблема.

Если кто-то с лучшим пониманием стандарта может доказать, что я неправ, я был бы признателен за просветление.

...