Может ли кто-нибудь объяснить мне, где именно setjmp()
и longjmp()
функции можно использовать практически во встроенном программировании? Я знаю, что это для обработки ошибок. Но хотелось бы знать несколько вариантов использования.
99
longjmp()
чтобы выйти из обработчика сигналов, особенно таких вещей, как файлBUS ERROR
. Этот сигнал обычно не может быть перезапущен. Встроенное приложение может захотеть обработать этот случай для безопасности и надежной работы.setjmp
между BSD и Linux, см. «Timing setjmp и радость стандартов» , где предлагается использоватьsigsetjmp
.Ответы:
Обработка ошибок
Предположим, что глубоко внутри функции, вложенной во многие другие функции, есть ошибка, и обработка ошибок имеет смысл только в функции верхнего уровня.
Было бы очень утомительно и неудобно, если бы все промежуточные функции должны были нормально возвращаться и оценивать возвращаемые значения или глобальную переменную ошибки, чтобы определить, что дальнейшая обработка не имеет смысла или даже будет плохой.
Это ситуация, когда имеет смысл setjmp / longjmp. Эти ситуации аналогичны ситуации, когда исключение на других языках (C ++, Java) имеет смысл.
Сопрограммы
Помимо обработки ошибок, я могу подумать и о другой ситуации, когда вам понадобится setjmp / longjmp в C:
Это тот случай, когда вам нужно реализовать сопрограммы .
Вот небольшой демонстрационный пример. Я надеюсь, что он удовлетворит запрос Sivaprasad Palas о некотором примере кода и ответит на вопрос TheBlastOne, как setjmp / longjmp поддерживает реализацию сопрограмм (насколько я вижу, это не основано на каком-либо нестандартном или новом поведении).
РЕДАКТИРОВАТЬ:
Возможно, на самом деле это поведение undefined для выполнения
longjmp
вниз стека вызовов (см. Комментарий MikeMB; хотя у меня еще не было возможности проверить это).#include <stdio.h> #include <setjmp.h> 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
. Это нестандартно.routineA
иroutineB
используется один и тот же стек, он работает только для очень примитивных сопрограмм. Если после первого вызоваroutineA
вызывает глубоко вложенный объект, а он выполняется как сопрограмма, то может даже уничтожить стек возврата (не только локальные переменные) . Таким образом, без выделения эксклюзивного стека (через после вызова ?) У вас возникнут серьезные проблемы с этим примером, если он используется в качестве рецепта.routineC
routineB
routineC
routineB
routineB
routineC
alloca()
rountineB
Теоретически вы можете использовать их для обработки ошибок, чтобы вы могли выйти из глубоко вложенной цепочки вызовов без необходимости иметь дело с обработкой ошибок в каждой функции в цепочке.
Как и любая умная теория, эта теория разваливается при встрече с реальностью. Ваши промежуточные функции будут выделять память, захватывать блокировки, открывать файлы и выполнять всевозможные вещи, требующие очистки. Так что на практике
setjmp
/longjmp
обычно являются плохой идеей, за исключением очень ограниченных случаев, когда вы полностью контролируете свою среду (некоторые встроенные платформы).По моему опыту, в большинстве случаев, когда вы думаете, что использование
setjmp
/longjmp
будет работать, ваша программа ясна и достаточно проста, чтобы каждый промежуточный вызов функции в цепочке вызовов мог обрабатывать ошибки, или это настолько беспорядочно и невозможно исправить, что вы должны делать,exit
когда вы столкнулись с ошибкой.источник
libjpeg
. Как и в C ++, большинство наборов процедур C используют astruct *
для работы с чем-то как с коллективом. Вместо того, чтобы хранить выделения памяти промежуточных функций как локальные, их можно сохранить в структуре. Это позволяетlongjmp()
обработчику освободить память. Кроме того, здесь не так много взорванных таблиц исключений, чтобы все компиляторы C ++ по-прежнему генерировали 20 лет спустя.Like every clever theory this falls apart when meeting reality.
В самом деле, временное выделение и тому подобноеlongjmp()
усложняют задачу, так как в этом случае вам придетсяsetjmp()
выполнять несколько операций в стеке вызовов (один раз для каждой функции, которая должна выполнить какую-либо очистку перед ее завершением, а затем необходимо «повторно вызвать исключение» в соответствии сlongjmp()
исходным контекстом). Еще хуже, если эти ресурсы изменяются послеsetjmp()
, так как вы должны объявить их,volatile
чтобы предотвратитьlongjmp()
их уничтожение.Сочетание
setjmp
иlongjmp
есть «суперсилаgoto
». Используйте с КРАЙНЕЙ осторожностью. Однако, как объясняли другие, alongjmp
очень полезен, чтобы выйти из неприятной ситуации с ошибкой, когда вы хотитеget me back to the beginning
быстро, вместо того, чтобы возвращать сообщение об ошибке для 18 уровней функций.Однако, точно так же
goto
, но хуже того, вы должны быть ДЕЙСТВИТЕЛЬНО осторожны при использовании этого. Alongjmp
просто вернет вас к началу кода. Это не повлияет на все остальные состояния, которые могли измениться междуsetjmp
и возвращением туда, откуда они былиsetjmp
начаты. Таким образом, выделения, блокировки, полуинициализированные структуры данных и т. Д. По-прежнему выделяются, блокируются и наполовину инициализируются, когда вы возвращаетесь туда, гдеsetjmp
было вызвано. Это означает, что вы должны действительно заботиться о местах, где вы это делаете, что ДЕЙСТВИТЕЛЬНО нормально звонить,longjmp
не вызывая БОЛЬШЕ проблем. Конечно, если следующее, что вы сделаете, - это «перезагрузка» [возможно, после сохранения сообщения об ошибке] - например, во встроенной системе, где вы обнаружили, что оборудование находится в плохом состоянии, тогда все в порядке.Я также видел
setjmp
/longjmp
использовал для предоставления очень простых механизмов многопоточности. Но это довольно частный случай - и определенно не то, как работают «стандартные» потоки.Изменить: можно, конечно, добавить код для «очистки», точно так же, как 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 ++». Но это довольно беспорядочно и зависит от хорошо написанного кода.
источник
setjmp
для защиты каждой инициализации, а-ля C ++… и стоит упомянуть, что использование ее для потоковой передачи нестандартно.Поскольку вы упомянули встроенные, я думаю, стоит отметить случай неиспользования : когда ваш стандарт кодирования запрещает это. Например, MISRA (MISRA-C: 2004: Правило 20.7) и JFS (Правило 20 AV): «Макрос setjmp и функция longjmp не должны использоваться».
источник
setjmp
иlongjmp
может быть очень полезен при модульном тестировании.Предположим, мы хотим протестировать следующий модуль:
#include <stdlib.h> int my_div(int x, int y) { if (y==0) exit(2); return x/y; }
Обычно, если тестируемая функция вызывает другую функцию, вы можете объявить для нее функцию-заглушку, которая будет имитировать то, что фактическая функция делает для тестирования определенных потоков. Однако в этом случае функция вызывает,
exit
который не возвращается. Заглушке нужно как-то имитировать такое поведение.setjmp
иlongjmp
могу сделать это за вас.Чтобы проверить эту функцию, мы можем создать следующую тестовую программу:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <setjmp.h> // 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
для этого вызов. Если вы этого не сделаете, ваша тестовая программа может не завершиться должным образом.источник
Я написал Java-подобный механизм обработки исключения в C , используя
setjmp()
,longjmp()
и системные функции. Он перехватывает пользовательские исключения, но также сигнализирует, какSIGSEGV
. Он имеет бесконечное вложение блоков обработки исключений, которое работает с вызовами функций, и поддерживает две наиболее распространенные реализации потоковой передачи. Он позволяет вам определить древовидную иерархию классов исключений, которые имеют наследование во время компоновки, и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("", "")); }
А вот часть включаемого файла, содержащая много логики:
#ifndef _EXCEPT_H #define _EXCEPT_H #include <stdlib.h> #include <stdio.h> #include <signal.h> #include <setjmp.h> #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.
Скажите мне, если вам интересно.
источник
main()
выйти из неперехваченного исключения. Проголосуйте за этот ответ :-)Progagation
Раздел в README. Я разместил свой код от апреля 1999 г. на GitHub (см. Ссылку в отредактированном ответе). Взглянуть; это был крепкий орешек. Было бы неплохо услышать, что вы думаете.Несомненно, наиболее важное использование setjmp / longjmp заключается в том, что он выполняет «нелокальный переход». Команда Goto (и есть редкие случаи, когда вам нужно будет использовать циклы goto over for и while) наиболее безопасно и безопасно используется в той же области. Если вы используете goto для перехода между областями (или автоматическим распределением), вы, скорее всего, повредите стек своей программы. setjmp / longjmp позволяет избежать этого, сохраняя информацию стека в том месте, куда вы хотите перейти. Затем, когда вы прыгаете, он загружает эту информацию о стеке. Без этой функции программистам на C, скорее всего, пришлось бы обратиться к программированию на ассемблере для решения проблем, которые мог решить только setjmp / longjmp. Слава богу, есть. Все в библиотеке C чрезвычайно важно. Вы будете знать, когда вам это нужно.
источник
Помимо обработки ошибок, еще одна вещь, которую вы можете сделать и о которой ранее не упоминалось, - это разумно реализовать хвостовое рекурсивное вычисление в C.
Именно так реализованы продолжения в C без преобразования входного кода в стиль передачи продолжения.
источник