Практическое использование setjmp и longjmp в C
Может ли кто-нибудь объяснить мне, где именно функции setjmp()
и longjmp()
могут использоваться практически во встроенном программировании? Я знаю, что они предназначены для обработки ошибок. Но я хотел бы знать некоторые варианты использования.
- Как преобразование float / double в int обрабатывается в printf?
- Почему мой оператор мощности (^) не работает?
- Как преобразовать символ ASCII в CGKeyCode?
- Массивы, распадающиеся на указатели
- Синхронизированный метод Java на объекте или методе?
- C: Приведение минимального 32-битного целого числа (-2147483648) к float дает положительное число (2147483648.0)
- возвращает указатель на литеральный (или постоянный) массив символов (строка)?
- Как представить номер FLOAT в памяти на C
Обработка ошибок
Предположим, что в функции, вложенной во многие другие функции, есть ошибка, и обработка ошибок имеет смысл только в функции верхнего уровня.
Было бы очень утомительно и неудобно, если бы все функции в промежутке должны были нормально возвращаться и оценивать возвращаемые значения или глобальную переменную ошибки, чтобы определить, что дальнейшая обработка не имеет смысла или даже будет плохим.
Это ситуация, когда setjmp / longjmp имеет смысл. Эти ситуации похожи на ситуацию, когда смысл в других языках (C ++, Java) имеет смысл.
Сопрограммы
Помимо обработки ошибок, я могу думать и о другой ситуации, когда вам нужен setjmp / longjmp в C:
Это тот случай, когда вам нужно реализовать сопрограммы .
Вот небольшой демонстрационный пример. Надеюсь, он удовлетворяет запросу Sivaprasad Palas для примера кода и отвечает на вопрос TheBlastOne, как setjmp / longjmp поддерживает реализацию корректов (насколько я вижу, он не основан на каком-то нестандартном или новом поведении).
РЕДАКТИРОВАТЬ:
Возможно, на самом деле это неопределенное поведение, чтобы сделать longjmp
по стоп-косту (см. Комментарий MikeMB, хотя я еще не имел возможности проверить это).
#include #include jmp_buf bufferA, bufferB; void routineB(); // forward declaration void routineA() { int r ; printf("(A1)\n"); r = setjmp(bufferA); if (r == 0) routineB(); printf("(A2) r=%d\n",r); r = setjmp(bufferA); if (r == 0) longjmp(bufferB, 20001); printf("(A3) r=%d\n",r); r = setjmp(bufferA); if (r == 0) longjmp(bufferB, 20002); printf("(A4) r=%d\n",r); } void routineB() { int r; printf("(B1)\n"); r = setjmp(bufferB); if (r == 0) longjmp(bufferA, 10001); printf("(B2) r=%d\n", r); r = setjmp(bufferB); if (r == 0) longjmp(bufferA, 10002); printf("(B3) r=%d\n", r); r = setjmp(bufferB); if (r == 0) longjmp(bufferA, 10003); } int main(int argc, char **argv) { routineA(); return 0; }
На следующем рисунке показан ход выполнения:
Внимание к сведению
При использовании setjmp / longjmp следует знать, что они влияют на справедливость локальных переменных, которые часто не рассматриваются.
Ср мой вопрос по этой теме .
Теория состоит в том, что вы можете использовать их для обработки ошибок, чтобы вы могли выпрыгнуть из глубоко вложенной цепочки вызовов, не занимаясь обработкой ошибок в каждой функции в цепочке.
Как всякая умная теория, это разваливается, когда встречается с реальностью. Ваши промежуточные функции будут выделять память, захватывать блокировки, открывать файлы и делать всевозможные вещи, требующие очистки. Поэтому на практике setjmp
/ longjmp
– это, как правило, плохая идея, за исключением очень ограниченных ситуаций, когда у вас есть полный контроль над вашей средой (некоторые встроенные платформы).
По моему опыту в большинстве случаев, когда вы думаете, что использование setjmp
/ longjmp
будет работать, ваша программа понятна и достаточно проста, чтобы каждый вызов промежуточной функции в цепочке вызовов мог выполнять обработку ошибок, или это настолько грязно и невозможно исправить, что вы должны делать когда вы столкнулись с ошибкой.
Комбинация setjmp
и longjmp
– это «суперпрочность goto
». Используйте с ЭКСТРЕМАЛЬНОЙ заботой. Однако, как объясняли другие, longjmp
очень полезен, чтобы избавиться от неприятной ситуации с ошибкой, когда вы хотите get me back to the beginning
, вместо того, чтобы просачивать сообщение об ошибке для 18 уровней функций.
Однако, как и goto
, но хуже, вы должны быть ДЕЙСТВИТЕЛЬНО осторожны, как вы это используете. longjmp
вернет вас к началу кода. Это не повлияет на все другие состояния, которые могут измениться между setjmp
и вернуться к setjmp
запуска setjmp
. Таким образом, выделения, блокировки, полуинициализированные структуры данных и т. Д. По-прежнему выделяются, блокируются и частично инициализируются, когда вы возвращаетесь туда, где был вызван setjmp
. Это означает, что вам нужно по-настоящему заботиться о местах, где вы это делаете, что ДЕЙСТВИТЕЛЬНО нормально называть longjmp
не вызывая БОЛЕЕ проблем. Конечно, если следующая вещь, которую вы делаете, это «перезагрузка» [после сохранения сообщения об ошибке, возможно] – во встроенной системе, где вы обнаружили, что аппаратное обеспечение находится в плохом состоянии, например, тогда это нормально.
Я также видел, что setjmp
/ longjmp
используется для обеспечения основных механизмов streamов. Но это довольно частный случай – и определенно не так, как работают «стандартные» streamи.
Редактирование. Конечно, можно добавить код для «обработки очистки», так же, как C ++ хранит точки исключения в скомпилированном коде, а затем знает, что дало исключение и что нужно очистить. Это будет связано с какой-то таблицей указателей функций и сохранением «если мы выпрыгнем снизу сюда, вызовите эту функцию с помощью этого аргумента». Что-то вроде этого:
struct { void (*destructor)(void *ptr); }; void LockForceUnlock(void *vlock) { LOCK* lock = vlock; } LOCK func_lock; void func() { ref = add_destructor(LockForceUnlock, mylock); Lock(func_lock) ... func2(); // May call longjmp. Unlock(func_lock); remove_destructor(ref); }
С помощью этой системы вы можете выполнить «полную обработку исключений, такую как C ++». Но это довольно грязно и полагается на хорошо написанный код.
Поскольку вы упоминаете встроенные, я думаю, что стоит отметить случай использования : когда ваш стандарт кодирования запрещает его. Например, MISRA (MISRA-C: 2004: Правило 20.7) и JFS (правило AV): «Маска setjmp и функция longjmp не должны использоваться».
setjmp
и longjmp
могут быть очень полезны при модульном тестировании.
Предположим, мы хотим протестировать следующий модуль:
#include int my_div(int x, int y) { if (y==0) exit(2); return x/y; }
Обычно, если функция проверки вызывает другую функцию, вы можете объявить функцию-заглушку для вызова, которая будет имитировать действительную функцию для проверки определенных streamов. Однако в этом случае функция вызывает exit
который не возвращается. Штук должен каким-то образом подражать этому поведению. setjmp
и longjmp
могут сделать это за вас.
Чтобы проверить эту функцию, мы можем создать следующую тестовую программу:
#include #include #include #include // redefine assert to set a boolean flag #ifdef assert #undef assert #endif #define assert(x) (rslt = rslt && (x)) // the function to test int my_div(int x, int y); // main result return code used by redefined assert static int rslt; // variables controling stub functions static int expected_code; static int should_exit; static jmp_buf jump_env; // test suite main variables static int done; static int num_tests; static int tests_passed; // utility function void TestStart(char *name) { num_tests++; rslt = 1; printf("-- Testing %s ... ",name); } // utility function void TestEnd() { if (rslt) tests_passed++; printf("%s\n", rslt ? "success" : "fail"); } // stub function void exit(int code) { if (!done) { assert(should_exit==1); assert(expected_code==code); longjmp(jump_env, 1); } else { _exit(code); } } // test case void test_normal() { int jmp_rval; int r; TestStart("test_normal"); should_exit = 0; if (!(jmp_rval=setjmp(jump_env))) { r = my_div(12,3); } assert(jmp_rval==0); assert(r==4); TestEnd(); } // test case void test_div0() { int jmp_rval; int r; TestStart("test_div0"); should_exit = 1; expected_code = 2; if (!(jmp_rval=setjmp(jump_env))) { r = my_div(2,0); } assert(jmp_rval==1); TestEnd(); } int main() { num_tests = 0; tests_passed = 0; done = 0; test_normal(); test_div0(); printf("Total tests passed: %d\n", tests_passed); done = 1; return !(tests_passed == num_tests); }
В этом примере вы используете setjmp
перед тем, как вводить функцию для тестирования, а затем на заглушенном exit
вы вызываете longjmp
чтобы вернуться обратно к вашему тестовому примеру.
Также обратите внимание, что переопределенный exit
имеет специальную переменную, которая проверяет, действительно ли вы хотите выйти из программы, и вызывает _exit
для этого. Если вы этого не сделаете, ваша тестовая программа может не выйти из строя.
setjmp () и longjmp () полезны для устранения ошибок и прерываний, встречающихся в низкоуровневой подпрограмме программы.
Я написал механизм обработки исключительных ситуаций в Java, используя функции setjmp()
, longjmp()
и system. Он ловит пользовательские исключения, но также сигналы, подобные SIGSEGV
. Он включает в себя бесконечное вложение блоков обработки исключений, которое работает на основе вызовов функций и поддерживает две наиболее распространенные реализации streamовой передачи. Он позволяет определить иерархию деревьев classов исключений, которые содержат наследование времени привязки, и оператор catch
ведет это дерево, чтобы проверить, нужно ли его перехватывать или передавать.
Вот пример того, как выглядит код с помощью этого:
try { *((int *)0) = 0; /* may not be portable */ } catch (SegmentationFault, e) { long f[] = { 'i', 'l', 'l', 'e', 'g', 'a', 'l' }; ((void(*)())f)(); /* may not be portable */ } finally { return(1 / strcmp("", "")); }
И вот часть файла include, который содержит много логики:
#ifndef _EXCEPT_H #define _EXCEPT_H #include #include #include #include #include "Lifo.h" #include "List.h" #define SETJMP(env) sigsetjmp(env, 1) #define LONGJMP(env, val) siglongjmp(env, val) #define JMP_BUF sigjmp_buf typedef void (* Handler)(int); typedef struct _Class *ClassRef; /* exception class reference */ struct _Class { int notRethrown; /* always 1 (used by throw()) */ ClassRef parent; /* parent class */ char * name; /* this class name string */ int signalNumber; /* optional signal number */ }; typedef struct _Class Class[1]; /* exception class */ typedef enum _Scope /* exception handling scope */ { OUTSIDE = -1, /* outside any 'try' */ INTERNAL, /* exception handling internal */ TRY, /* in 'try' (across routine calls) */ CATCH, /* in 'catch' (idem.) */ FINALLY /* in 'finally' (idem.) */ } Scope; typedef enum _State /* exception handling state */ { EMPTY, /* no exception occurred */ PENDING, /* exception occurred but not caught */ CAUGHT /* occurred exception caught */ } State; typedef struct _Except /* exception handle */ { int notRethrown; /* always 0 (used by throw()) */ State state; /* current state of this handle */ JMP_BUF throwBuf; /* start-'catching' destination */ JMP_BUF finalBuf; /* perform-'finally' destination */ ClassRef class; /* occurred exception class */ void * pData; /* exception associated (user) data */ char * file; /* exception file name */ int line; /* exception line number */ int ready; /* macro code control flow flag */ Scope scope; /* exception handling scope */ int first; /* flag if first try in function */ List * checkList; /* list used by 'catch' checking */ char* tryFile; /* source file name of 'try' */ int tryLine; /* source line number of 'try' */ ClassRef (*getClass)(void); /* method returning class reference */ char * (*getMessage)(void); /* method getting description */ void * (*getData)(void); /* method getting application data */ void (*printTryTrace)(FILE*);/* method printing nested trace */ } Except; typedef struct _Context /* exception context per thread */ { Except * pEx; /* current exception handle */ Lifo * exStack; /* exception handle stack */ char message[1024]; /* used by ExceptGetMessage() */ Handler sigAbrtHandler; /* default SIGABRT handler */ Handler sigFpeHandler; /* default SIGFPE handler */ Handler sigIllHandler; /* default SIGILL handler */ Handler sigSegvHandler; /* default SIGSEGV handler */ Handler sigBusHandler; /* default SIGBUS handler */ } Context; extern Context * pC; extern Class Throwable; #define except_class_declare(child, parent) extern Class child #define except_class_define(child, parent) Class child = { 1, parent, #child } except_class_declare(Exception, Throwable); except_class_declare(OutOfMemoryError, Exception); except_class_declare(FailedAssertion, Exception); except_class_declare(RuntimeException, Exception); except_class_declare(AbnormalTermination, RuntimeException); /* SIGABRT */ except_class_declare(ArithmeticException, RuntimeException); /* SIGFPE */ except_class_declare(IllegalInstruction, RuntimeException); /* SIGILL */ except_class_declare(SegmentationFault, RuntimeException); /* SIGSEGV */ except_class_declare(BusError, RuntimeException); /* SIGBUS */ #ifdef DEBUG #define CHECKED \ static int checked #define CHECK_BEGIN(pC, pChecked, file, line) \ ExceptCheckBegin(pC, pChecked, file, line) #define CHECK(pC, pChecked, class, file, line) \ ExceptCheck(pC, pChecked, class, file, line) #define CHECK_END \ !checked #else /* DEBUG */ #define CHECKED #define CHECK_BEGIN(pC, pChecked, file, line) 1 #define CHECK(pC, pChecked, class, file, line) 1 #define CHECK_END 0 #endif /* DEBUG */ #define except_thread_cleanup(id) ExceptThreadCleanup(id) #define try \ ExceptTry(pC, __FILE__, __LINE__); \ while (1) \ { \ Context * pTmpC = ExceptGetContext(pC); \ Context * pC = pTmpC; \ CHECKED; \ \ if (CHECK_BEGIN(pC, &checked, __FILE__, __LINE__) && \ pC->pEx->ready && SETJMP(pC->pEx->throwBuf) == 0) \ { \ pC->pEx->scope = TRY; \ do \ { #define catch(class, e) \ } \ while (0); \ } \ else if (CHECK(pC, &checked, class, __FILE__, __LINE__) && \ pC->pEx->ready && ExceptCatch(pC, class)) \ { \ Except *e = LifoPeek(pC->exStack, 1); \ pC->pEx->scope = CATCH; \ do \ { #define finally \ } \ while (0); \ } \ if (CHECK_END) \ continue; \ if (!pC->pEx->ready && SETJMP(pC->pEx->finalBuf) == 0) \ pC->pEx->ready = 1; \ else \ break; \ } \ ExceptGetContext(pC)->pEx->scope = FINALLY; \ while (ExceptGetContext(pC)->pEx->ready > 0 || ExceptFinally(pC)) \ while (ExceptGetContext(pC)->pEx->ready-- > 0) #define throw(pExceptOrClass, pData) \ ExceptThrow(pC, (ClassRef)pExceptOrClass, pData, __FILE__, __LINE__) #define return(x) \ { \ if (ExceptGetScope(pC) != OUTSIDE) \ { \ void * pData = malloc(sizeof(JMP_BUF)); \ ExceptGetContext(pC)->pEx->pData = pData; \ if (SETJMP(*(JMP_BUF *)pData) == 0) \ ExceptReturn(pC); \ else \ free(pData); \ } \ return x; \ } #define pending \ (ExceptGetContext(pC)->pEx->state == PENDING) extern Scope ExceptGetScope(Context *pC); extern Context *ExceptGetContext(Context *pC); extern void ExceptThreadCleanup(int threadId); extern void ExceptTry(Context *pC, char *file, int line); extern void ExceptThrow(Context *pC, void * pExceptOrClass, void *pData, char *file, int line); extern int ExceptCatch(Context *pC, ClassRef class); extern int ExceptFinally(Context *pC); extern void ExceptReturn(Context *pC); extern int ExceptCheckBegin(Context *pC, int *pChecked, char *file, int line); extern int ExceptCheck(Context *pC, int *pChecked, ClassRef class, char *file, int line); #endif /* _EXCEPT_H */
Существует также модуль C, который содержит логику обработки сигналов и некоторую бухгалтерскую отчетность.
Это было очень сложно реализовать, я могу сказать вам, и я почти ушел. Я действительно старался максимально приблизить его к Java; Мне было удивительно, как далеко я получил только с C.
Дай мне крик, если тебе интересно.