Практическое использование setjmp и longjmp в C

Может ли кто-нибудь объяснить мне, где именно функции setjmp() и longjmp() могут использоваться практически во встроенном программировании? Я знаю, что они предназначены для обработки ошибок. Но я хотел бы знать некоторые варианты использования.

Обработка ошибок
Предположим, что в функции, вложенной во многие другие функции, есть ошибка, и обработка ошибок имеет смысл только в функции верхнего уровня.

Было бы очень утомительно и неудобно, если бы все функции в промежутке должны были нормально возвращаться и оценивать возвращаемые значения или глобальную переменную ошибки, чтобы определить, что дальнейшая обработка не имеет смысла или даже будет плохим.

Это ситуация, когда 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.

Дай мне крик, если тебе интересно.

  • Присвоение отрицательных чисел неподписанному int?
  • «плавающая» и «двойная» точность
  • В C, почему несколько деклараций работают нормально для глобальной переменной, но не для локальной переменной?
  • После K & R, какую книгу можно использовать для обучения программированию на простом C?
  • Является ли модификация строковых литералов неопределенным поведением в соответствии со стандартом C89?
  • Поймать Ctrl-C в C
  • Несколько символов в символьной константе
  • Когда использовать бит-поля в C?
  • Почему FILE * не сохраняет адрес открытого файла
  • хранение денежных сумм в mysql
  • Как числа с плавающей запятой сохраняются в памяти?
  • Interesting Posts

    Как вызвать службу геокодирования Google из кода C #

    Пакет заменяет определенные строки #s в xml с определенным текстом (строками) из второго файла xml в Windows 7

    Каков наилучший способ сделать подстроку в пакетном файле?

    Настройка сети на Virtualbox Guest для доступа к VPN-хосту

    Укажите порядок сортировки в «copy / b * .dat foo» в Windows cmd

    Почему я не могу обработать массив как указатель в C?

    Как динамически запрашивать базу данных комнаты во время выполнения?

    Использование команды cd в командной строке Windows не может перейти к D: \

    Как взаимодействовать с BadgeProvider на телефонах Samsung, чтобы добавить счет к значку приложения?

    Как получить только время с даты-времени C #

    Эффективный, лаконичный способ найти следующий соответствующий друг?

    Ошибка MVC 4 Beta при сбое

    Можем ли мы создать экземпляр интерфейса на Java?

    Могу ли я установить заголовок по умолчанию для окна tmux?

    Как использовать GROUP_CONCAT в CONCAT в MySQL

    Давайте будем гением компьютера.