Longjmp и RAII
Итак, у меня есть библиотека (не написанная мной), которая, к сожалению, использует 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
более намного проще и понятнее, но сегодня утром мне пришла в голову мысль. Гарантировано ли это? Здесь мои мысли:
- Никакие переменные не изменяются или изменяются между вызовами
setjmp
/longjmp
.
- Я
longjmp
привязываюсь к местоположению в том же стеке стека, что и setjmp
и return
, как обычно, поэтому я разрешаю коду выполнять код очистки, который компилятор испускал в точках выхода функция.
- Кажется, он работает как ожидалось.
Но у меня все еще возникает ощущение, что это, скорее всего, поведение undefined. Что вы, ребята, думаете?
Ответы
Ответ 1
Я предполагаю, что f
находится в сторонней библиотеке/приложении, потому что иначе вы могли бы просто исправить это, чтобы не вызывать прерывание. Учитывая это, и что RAII может или не может надежно производить правильные результаты на всех платформах/компиляторах, у вас есть несколько вариантов.
- Создайте крошечный общий объект, который определяет
abort
и LD_PRELOAD. Затем вы контролируете, что происходит при прерывании, а НЕ в обработчике сигналов.
- Запустите
f
внутри подпроцесса. Затем вы просто проверяете код возврата, и если он не повторил попытку с обновленными вводами.
- Вместо использования RAII просто позвоните своему оригинальному
function
из нескольких точек вызова и дайте ему вручную выполнить настройку/разрывы явно. В этом случае он по-прежнему исключает копирование.
Ответ 2
Мне действительно нравится ваше решение и закодировано что-то подобное в тестовых жгутах, чтобы проверить, что целевая функция assert()
как ожидалось.
Я не вижу причин, по которым этот код вызывает поведение undefined. Стандарт C, похоже, благословляет его: обработчики, являющиеся результатом abort()
, освобождаются от ограничения вызова функций библиотеки из обработчика. (Предостережение: это 7.14.1.1 (5) из C99 - к сожалению, у меня нет копии C90, версии, на которую ссылается стандарт С++).
С++ 03 добавляет дополнительное ограничение: если какие-либо автоматические объекты будут уничтожены с помощью управления передачей исключенного исключения на другую (целевую) точку в программе, тогда вызов longjmp (jbuf, val) в точке бросания управление передачей в ту же (целевую) точку имеет поведение undefined. Я предполагаю, что ваше утверждение о том, что "никакие переменные не изменчивы или не изменяется между вызовами в setjmp/longjmp", включает в себя создание экземпляров любых автоматических объектов С++. (Я предполагаю, что это некоторая библиотека наследия C?).
Не является безопасным сигналом асинхронного сигнала POSIX (или его отсутствием), проблема - abort()
синхронно генерирует SIGABRT с выполнением программы.
Самая большая проблема будет искажать глобальное состояние стороннего кода: маловероятно, что автор приложит все усилия, чтобы согласовать состояние до abort()
. Но, если вы правы, что переменные не меняются, это не проблема.
Если кто-то, кто лучше разбирается в стандартах, может доказать, что я ошибаюсь, я буду благодарен за просвещение.