Czy ktoś może mi wyjaśnić, gdzie dokładnie setjmp()i longjmp()funkcje można praktycznie wykorzystać w programowaniu wbudowanym? Wiem, że te służą do obsługi błędów. Ale chciałbym poznać kilka przypadków użycia.
Dla prędkości? Tak. Ponieważ a) działa wolniej niż pętla i b) ponieważ nie można go łatwo zoptymalizować (jak usunięcie opóźnienia lub dwóch). Więc setjmp i longjmp wyraźnie rządzą!
TheBlastOne
Inną odpowiedzią niż te podane jest tutaj stackoverflow.com/questions/7334595/… Możesz użyć longjmp()do wyjścia z obsługi sygnału, szczególnie rzeczy takie jak a BUS ERROR. Ten sygnał zwykle nie może zostać ponownie uruchomiony. Wbudowana aplikacja może chcieć obsłużyć ten przypadek w celu zapewnienia bezpieczeństwa i niezawodnego działania.
Obsługa błędów
Załóżmy, że w funkcji zagnieżdżonej w wielu innych funkcjach występuje błąd, a obsługa błędów ma sens tylko w funkcji najwyższego poziomu.
Byłoby bardzo uciążliwe i niewygodne, gdyby wszystkie funkcje pomiędzy nimi musiały zwracać normalnie i oceniać zwracane wartości lub globalną zmienną błędu, aby określić, że dalsze przetwarzanie nie ma sensu lub nawet byłoby złe.
To sytuacja, w której setjmp / longjmp ma sens. Te sytuacje są podobne do sytuacji, w których wyjątki w innych językach (C ++, Java) mają sens.
Korekty
Oprócz obsługi błędów przychodzi mi do głowy jeszcze inna sytuacja, w której potrzebujesz setjmp / longjmp w C:
Tak jest w przypadku, gdy musisz wdrożyć programy .
Oto mały przykład demo. Mam nadzieję, że spełnia prośbę Sivaprasad Palas o przykładowy kod i odpowiada na pytanie TheBlastOne, w jaki sposób setjmp / longjmp obsługuje implementację procedur (o ile widzę, że nie opiera się na żadnym niestandardowym lub nowym zachowaniu).
EDIT:
To może być to, że faktycznie jest niezdefiniowane zachowanie zrobić longjmpdół callstack (patrz komentarz o MikeMB, choć jeszcze nie miałem okazji do sprawdzenia tego).
#include<stdio.h>#include<setjmp.h>
jmp_buf bufferA, bufferB;
voidroutineB(); // forward declaration voidroutineA(){
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);
}
voidroutineB(){
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);
}
intmain(int argc, char **argv){
routineA();
return0;
}
Poniższy rysunek przedstawia przebieg wykonania:
Uwaga ostrzegawcza
Używając setjmp / longjmp, należy pamiętać, że mają one wpływ na ważność zmiennych lokalnych, które często nie są brane pod uwagę.
Por. moje pytanie na ten temat .
Ponieważ setjmp przygotowuje, a longjmp wykonuje skok z zakresu bieżącego wywołania z powrotem do zakresu setjmp, w jaki sposób miałoby to wspierać implementację korektorów? Nie rozumiem, jak można by kontynuować wykonywanie procedury, która wyszła z longjmp.
TheBlastOne
2
@TheBlastOne Zobacz artykuł w Wikipedii . Możesz kontynuować egzekucję, jeśli jesteś setjmpprzed tobą longjmp. To jest niestandardowe.
Potatoswatter
11
Korekty muszą działać na osobnych stosach, a nie na tym samym, co pokazano w przykładzie. Jako routineAi routineBużywaj tego samego stosu, działa tylko dla bardzo prymitywnych coroutines. Jeśli routineAwywołuje głęboko zagnieżdżone routineCpo pierwszym wywołaniu do routineBi routineCdziała to routineBjako coroutine, routineBmoże nawet zniszczyć stos zwrotny (nie tylko zmienne lokalne) routineC. Więc bez przydzielania ekskluzywnego stosu ( alloca()po sprawdzeniu rountineB?), Będziesz miał poważne kłopoty z tym przykładem, jeśli użyjesz go jako przepisu.
Tino
7
Proszę wspomnieć w swojej odpowiedzi, że przeskakiwanie w dół stosu wywołań (z A do B) jest niezdefiniowanym zachowaniem).
MikeMB
2
To jest rzeczywiście nieokreślone. Będziesz musiał uruchomić każdą funkcję na swoim własnym niezależnym stosie, aby z wdziękiem przełączać konteksty
Curious
18
Teoria mówi, że można ich używać do obsługi błędów, dzięki czemu można wyskoczyć z głęboko zagnieżdżonego łańcucha wywołań bez konieczności radzenia sobie z błędami w każdej funkcji w łańcuchu.
Jak każda mądra teoria, ta rozpada się, gdy spotyka się z rzeczywistością. Twoje funkcje pośrednie będą alokować pamięć, przechwytywać blokady, otwierać pliki i robić różne rzeczy, które wymagają czyszczenia. Tak więc w praktyce setjmp/ longjmpsą zwykle złym pomysłem, z wyjątkiem bardzo nielicznych sytuacji, w których masz całkowitą kontrolę nad swoim środowiskiem (niektóre platformy wbudowane).
Z mojego doświadczenia wynika, że w większości przypadków, gdy myślisz, że użycie setjmp/ longjmpzadziała, twój program jest na tyle jasny i prosty, że każde wywołanie funkcji pośredniej w łańcuchu wywołań może obsługiwać błędy, lub jest tak niechlujne i niemożliwe do naprawienia, że powinieneś zrobić, exitgdy napotkać błąd.
Proszę spojrzeć libjpeg. Podobnie jak w C ++, większość kolekcji procedur C struct *wykorzystuje coś jako kolektyw. Zamiast przechowywać alokacje pamięci funkcji pośrednich jako lokalne, można je przechowywać w strukturze. Pozwala to longjmp()programowi obsługi zwolnić pamięć. Ponadto nie ma tak wielu przeklętych tabel wyjątków, które wszystkie kompilatory C ++ nadal generują 20 lat po fakcie.
bezartowy hałas
Like every clever theory this falls apart when meeting reality.Rzeczywiście, tymczasowa alokacja i tym podobne sprawiają, longjmp()że jest to trudne, ponieważ wtedy musisz setjmp()wielokrotnie znajdować się na stosie wywołań (raz na każdą funkcję, która musi wykonać jakieś czyszczenie przed zakończeniem, a następnie musi „ponownie zgłosić wyjątek” biorąc pod longjmp()uwagę kontekst, który otrzymała na początku). Jeszcze gorzej jest, jeśli te zasoby zostaną zmodyfikowane po tym setjmp(), jak musisz je zadeklarować, volatileaby zapobiec longjmp()ich zbiciu.
sevko
10
Połączenie setjmpi longjmpto „super siła goto”. Używaj z EKSTREMALNĄ ostrożnością. Jednak, jak wyjaśnili inni, a longjmpjest bardzo przydatny do wyjścia z nieprzyjemnej sytuacji błędu, gdy chcesz get me back to the beginningszybko, zamiast przesyłać komunikat o błędzie dla 18 warstw funkcji.
Jednak tak jak goto, ale gorzej, musisz NAPRAWDĘ uważać, jak tego używasz. A longjmpspowoduje po prostu powrót do początku kodu. Nie wpłynie to na wszystkie inne stany, które mogły ulec zmianie między setjmpdniem a powrotem do miejsca setjmprozpoczęcia. Tak więc alokacje, blokady, w połowie zainicjowane struktury danych itp. Są nadal przydzielane, blokowane i w połowie inicjowane, gdy wracasz do miejsca, w którym setjmpzostało wywołane. Oznacza to, że musisz naprawdę dbać o miejsca, w których to robisz, aby NAPRAWDĘ zadzwonić longjmpbez powodowania WIĘCEJ problemów. Oczywiście, jeśli następną rzeczą, jaką zrobisz, jest „ponowne uruchomienie” (być może po zapisaniu komunikatu o błędzie) - w systemie wbudowanym, w którym odkryłeś, że sprzęt jest na przykład w złym stanie, to w porządku.
Widziałem również setjmp/ longjmpkorzystałem z bardzo podstawowych mechanizmów gwintowania. Ale to dość szczególny przypadek - a na pewno nie sposób działania „standardowych” wątków.
Edycja: Można oczywiście dodać kod, aby „zająć się czyszczeniem”, w ten sam sposób, w jaki C ++ przechowuje punkty wyjątków w skompilowanym kodzie, a następnie wie, co spowodowało wyjątek, a co wymaga oczyszczenia. Wymagałoby to pewnego rodzaju tablicy wskaźników funkcji i przechowywania „jeśli wyskoczymy stąd z dołu, wywołaj tę funkcję z tym argumentem”. Coś takiego:
+1, oczywiście, teoretycznie można by zaimplementować czystą obsługę wyjątków przez wywołanie setjmpochrony każdej inicjalizacji, a la C ++… i warto wspomnieć, że używanie go do tworzenia wątków jest niestandardowe.
Potatoswatter
8
Ponieważ wspomniałeś o osadzeniu, myślę, że warto zwrócić uwagę na przypadek nieużywania : kiedy zabrania tego twój standard kodowania. Na przykład MISRA (MISRA-C: 2004: Rule 20.7) i JFS (AV Rule 20): „Makro setjmp i funkcja longjmp nie powinny być używane”.
setjmpi longjmpmoże być bardzo przydatne w testowaniu jednostkowym.
Załóżmy, że chcemy przetestować następujący moduł:
#include<stdlib.h>intmy_div(int x, int y){
if (y==0) exit(2);
return x/y;
}
Zwykle, jeśli testowana funkcja wywołuje inną funkcję, możesz zadeklarować funkcję pośredniczącą, która będzie naśladować to, co robi rzeczywista funkcja, aby przetestować określone przepływy. W tym przypadku jednak wywołuje funkcję, exitktóra nie zwraca. Kod stub musi w jakiś sposób naśladować to zachowanie. setjmpi longjmpmogę to dla Ciebie zrobić.
Aby przetestować tę funkcję, możemy stworzyć następujący program testowy:
#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 testintmy_div(int x, int y);
// main result return code used by redefined assertstaticint rslt;
// variables controling stub functionsstaticint expected_code;
staticint should_exit;
static jmp_buf jump_env;
// test suite main variablesstaticint done;
staticint num_tests;
staticint tests_passed;
// utility functionvoidTestStart(char *name){
num_tests++;
rslt = 1;
printf("-- Testing %s ... ",name);
}
// utility functionvoidTestEnd(){
if (rslt) tests_passed++;
printf("%s\n", rslt ? "success" : "fail");
}
// stub functionvoidexit(int code){
if (!done)
{
assert(should_exit==1);
assert(expected_code==code);
longjmp(jump_env, 1);
}
else
{
_exit(code);
}
}
// test casevoidtest_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 casevoidtest_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();
}
intmain(){
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);
}
W tym przykładzie użyjesz setjmpprzed wejściem do funkcji do testowania, a następnie exitwywołasz skrót , longjmpaby powrócić bezpośrednio do przypadku testowego.
Zauważ również, że przedefiniowana exitma specjalną zmienną, którą sprawdza, czy rzeczywiście chcesz wyjść z programu i wywołuje _exitw tym celu. Jeśli tego nie zrobisz, program testowy może nie zakończyć się poprawnie.
Napisałem Java podobną wyjątkiem mechanizmu manipulacyjną w C z użyciem setjmp(), longjmp()oraz funkcji systemowych. Wyłapuje niestandardowe wyjątki, ale także sygnały takie jak SIGSEGV. Oferuje nieskończone zagnieżdżanie bloków obsługi wyjątków, które działa na wywołania funkcji i obsługuje dwie najpopularniejsze implementacje wątków. Umożliwia zdefiniowanie hierarchii drzewiastej klas wyjątków, które mają dziedziczenie w czasie łączenia, a catchinstrukcja przechodzi przez to drzewo, aby sprawdzić, czy musi zostać przechwycone lub przekazane dalej.
Oto przykład, jak wygląda kod przy użyciu tego:
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("", ""));
}
A oto część pliku dołączanego, która zawiera dużo logiki:
#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_buftypedefvoid(* Handler)(int);
typedefstruct _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 */
};
typedefstruct _ClassClass[1];/* exception class */typedefenum _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;
typedefenum _State /* exception handling state */
{
EMPTY, /* no exception occurred */
PENDING, /* exception occurred but not caught */
CAUGHT /* occurred exception caught */
} State;
typedefstruct _Except /* exceptionhandle */
{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;
typedefstruct _Context /* exceptioncontextperthread */
{
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); \
} \
elseif (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);
externvoidExceptThreadCleanup(int threadId);
externvoidExceptTry(Context *pC, char *file, int line);
externvoidExceptThrow(Context *pC, void * pExceptOrClass,
void *pData, char *file, int line);
externintExceptCatch(Context *pC, ClassRef class);
externintExceptFinally(Context *pC);
externvoidExceptReturn(Context *pC);
externintExceptCheckBegin(Context *pC, int *pChecked,
char *file, int line);
externintExceptCheck(Context *pC, int *pChecked, ClassRef class,
char *file, int line);
#endif/* _EXCEPT_H */
Istnieje również moduł C, który zawiera logikę do obsługi sygnałów i trochę księgowości.
To było niezwykle trudne do wdrożenia, mogę powiedzieć i prawie zrezygnowałem. Naprawdę starałem się, aby było jak najbliżej Javy; Zaskakujące było, jak daleko zaszedłem mając tylko C.
Jestem zaskoczony, że jest to możliwe bez faktycznej obsługi kompilatora dla niestandardowych wyjątków. Ale naprawdę interesujące jest to, w jaki sposób sygnały przekształcają się w wyjątki.
Paul Stelian
Zapytam jedno: a co z wyjątkami, które nigdy nie zostaną złapane? Jak zakończy się main ()?
Paul Stelian
1
@PaulStelian A oto twoja odpowiedź na pytanie, w jaki sposób main()wyjdzie z niezłapanego wyjątku . Proszę, zagłosuj na tę odpowiedź :-)
znaczenie-ma znaczenie
1
@PaulStelian Ah, teraz rozumiem, co masz na myśli. Wydaje mi się, że wyjątki w czasie wykonywania, które nie są przechwytywane, zostały ponownie zgłoszone, więc zastosowanie ma odpowiedź ogólna (zależna od platformy). Nie wychwycone wyjątki niestandardowe zostały wydrukowane i zignorowane. Zobacz Progagationsekcję w README. Wysłałem swój kod z kwietnia 1999 na GitHub (zobacz link w zredagowanej odpowiedzi). Spójrz; to był twardy orzech do zgryzienia. Byłoby miło usłyszeć, co myślisz.
znaczenie-ma znaczenie
2
Rzuciłem krótkie spojrzenie na README, całkiem niezły. Zasadniczo więc propaguje się do najbardziej zewnętrznego bloku try i jest zgłaszany, podobnie jak funkcje asynchroniczne JavaScript. Ładny. Później przyjrzę się samemu kodowi źródłowemu.
Paul Stelian
1
Bez dwóch zdań, najważniejszym zastosowaniem setjmp / longjmp jest to, że działa jako „nielokalny skok goto”. Polecenie Goto (i są rzadkie przypadki, w których będziesz musiał użyć pętli goto over for i while) jest najczęściej używane - bezpiecznie w tym samym zakresie. Jeśli używasz goto do przeskakiwania między zakresami (lub automatycznej alokacji), najprawdopodobniej uszkodzisz stos swojego programu. setjmp / longjmp pozwala tego uniknąć, zapisując informacje o stosie w miejscu, do którego chcesz przejść. Następnie podczas skoku ładuje informacje o stosie. Bez tej funkcji programiści C najprawdopodobniej musieliby zwrócić się do programowania w asemblerze, aby rozwiązać problemy, które może rozwiązać tylko setjmp / longjmp. Dzięki Bogu, że istnieje. Wszystko w bibliotece C jest niezwykle ważne. Dowiesz się, kiedy tego potrzebujesz.
„Wszystko w bibliotece C jest niezwykle ważne”. Jest cała masa przestarzałych rzeczy i rzeczy, które nigdy nie były dobre, na przykład ustawienia regionalne.
qwr
0
Oprócz obsługi błędów, inną rzeczą, którą możesz zrobić i której wcześniej nie wspomniano, jest inteligentne zaimplementowanie obliczeń rekurencyjnych ogona w języku C.
W ten sposób są implementowane kontynuacje w C bez konwertowania kodu wejściowego w stylu przekazywania kontynuacji.
longjmp()
do wyjścia z obsługi sygnału, szczególnie rzeczy takie jak aBUS ERROR
. Ten sygnał zwykle nie może zostać ponownie uruchomiony. Wbudowana aplikacja może chcieć obsłużyć ten przypadek w celu zapewnienia bezpieczeństwa i niezawodnego działania.setjmp
między BSD i Linuksem, zobacz „Timing setjmp, and the Joy of Standards” , który sugeruje użyciesigsetjmp
.Odpowiedzi:
Obsługa błędów
Załóżmy, że w funkcji zagnieżdżonej w wielu innych funkcjach występuje błąd, a obsługa błędów ma sens tylko w funkcji najwyższego poziomu.
Byłoby bardzo uciążliwe i niewygodne, gdyby wszystkie funkcje pomiędzy nimi musiały zwracać normalnie i oceniać zwracane wartości lub globalną zmienną błędu, aby określić, że dalsze przetwarzanie nie ma sensu lub nawet byłoby złe.
To sytuacja, w której setjmp / longjmp ma sens. Te sytuacje są podobne do sytuacji, w których wyjątki w innych językach (C ++, Java) mają sens.
Korekty
Oprócz obsługi błędów przychodzi mi do głowy jeszcze inna sytuacja, w której potrzebujesz setjmp / longjmp w C:
Tak jest w przypadku, gdy musisz wdrożyć programy .
Oto mały przykład demo. Mam nadzieję, że spełnia prośbę Sivaprasad Palas o przykładowy kod i odpowiada na pytanie TheBlastOne, w jaki sposób setjmp / longjmp obsługuje implementację procedur (o ile widzę, że nie opiera się na żadnym niestandardowym lub nowym zachowaniu).
EDIT:
To może być to, że faktycznie jest niezdefiniowane zachowanie zrobić
longjmp
dół callstack (patrz komentarz o MikeMB, choć jeszcze nie miałem okazji do sprawdzenia tego).#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; }
Poniższy rysunek przedstawia przebieg wykonania:
Uwaga ostrzegawcza
Używając setjmp / longjmp, należy pamiętać, że mają one wpływ na ważność zmiennych lokalnych, które często nie są brane pod uwagę.
Por. moje pytanie na ten temat .
źródło
setjmp
przed tobąlongjmp
. To jest niestandardowe.routineA
iroutineB
używaj tego samego stosu, działa tylko dla bardzo prymitywnych coroutines. JeśliroutineA
wywołuje głęboko zagnieżdżoneroutineC
po pierwszym wywołaniu doroutineB
iroutineC
działa toroutineB
jako coroutine,routineB
może nawet zniszczyć stos zwrotny (nie tylko zmienne lokalne)routineC
. Więc bez przydzielania ekskluzywnego stosu (alloca()
po sprawdzeniurountineB
?), Będziesz miał poważne kłopoty z tym przykładem, jeśli użyjesz go jako przepisu.Teoria mówi, że można ich używać do obsługi błędów, dzięki czemu można wyskoczyć z głęboko zagnieżdżonego łańcucha wywołań bez konieczności radzenia sobie z błędami w każdej funkcji w łańcuchu.
Jak każda mądra teoria, ta rozpada się, gdy spotyka się z rzeczywistością. Twoje funkcje pośrednie będą alokować pamięć, przechwytywać blokady, otwierać pliki i robić różne rzeczy, które wymagają czyszczenia. Tak więc w praktyce
setjmp
/longjmp
są zwykle złym pomysłem, z wyjątkiem bardzo nielicznych sytuacji, w których masz całkowitą kontrolę nad swoim środowiskiem (niektóre platformy wbudowane).Z mojego doświadczenia wynika, że w większości przypadków, gdy myślisz, że użycie
setjmp
/longjmp
zadziała, twój program jest na tyle jasny i prosty, że każde wywołanie funkcji pośredniej w łańcuchu wywołań może obsługiwać błędy, lub jest tak niechlujne i niemożliwe do naprawienia, że powinieneś zrobić,exit
gdy napotkać błąd.źródło
libjpeg
. Podobnie jak w C ++, większość kolekcji procedur Cstruct *
wykorzystuje coś jako kolektyw. Zamiast przechowywać alokacje pamięci funkcji pośrednich jako lokalne, można je przechowywać w strukturze. Pozwala tolongjmp()
programowi obsługi zwolnić pamięć. Ponadto nie ma tak wielu przeklętych tabel wyjątków, które wszystkie kompilatory C ++ nadal generują 20 lat po fakcie.Like every clever theory this falls apart when meeting reality.
Rzeczywiście, tymczasowa alokacja i tym podobne sprawiają,longjmp()
że jest to trudne, ponieważ wtedy musiszsetjmp()
wielokrotnie znajdować się na stosie wywołań (raz na każdą funkcję, która musi wykonać jakieś czyszczenie przed zakończeniem, a następnie musi „ponownie zgłosić wyjątek” biorąc podlongjmp()
uwagę kontekst, który otrzymała na początku). Jeszcze gorzej jest, jeśli te zasoby zostaną zmodyfikowane po tymsetjmp()
, jak musisz je zadeklarować,volatile
aby zapobieclongjmp()
ich zbiciu.Połączenie
setjmp
ilongjmp
to „super siłagoto
”. Używaj z EKSTREMALNĄ ostrożnością. Jednak, jak wyjaśnili inni, alongjmp
jest bardzo przydatny do wyjścia z nieprzyjemnej sytuacji błędu, gdy chceszget me back to the beginning
szybko, zamiast przesyłać komunikat o błędzie dla 18 warstw funkcji.Jednak tak jak
goto
, ale gorzej, musisz NAPRAWDĘ uważać, jak tego używasz. Alongjmp
spowoduje po prostu powrót do początku kodu. Nie wpłynie to na wszystkie inne stany, które mogły ulec zmianie międzysetjmp
dniem a powrotem do miejscasetjmp
rozpoczęcia. Tak więc alokacje, blokady, w połowie zainicjowane struktury danych itp. Są nadal przydzielane, blokowane i w połowie inicjowane, gdy wracasz do miejsca, w którymsetjmp
zostało wywołane. Oznacza to, że musisz naprawdę dbać o miejsca, w których to robisz, aby NAPRAWDĘ zadzwonićlongjmp
bez powodowania WIĘCEJ problemów. Oczywiście, jeśli następną rzeczą, jaką zrobisz, jest „ponowne uruchomienie” (być może po zapisaniu komunikatu o błędzie) - w systemie wbudowanym, w którym odkryłeś, że sprzęt jest na przykład w złym stanie, to w porządku.Widziałem również
setjmp
/longjmp
korzystałem z bardzo podstawowych mechanizmów gwintowania. Ale to dość szczególny przypadek - a na pewno nie sposób działania „standardowych” wątków.Edycja: Można oczywiście dodać kod, aby „zająć się czyszczeniem”, w ten sam sposób, w jaki C ++ przechowuje punkty wyjątków w skompilowanym kodzie, a następnie wie, co spowodowało wyjątek, a co wymaga oczyszczenia. Wymagałoby to pewnego rodzaju tablicy wskaźników funkcji i przechowywania „jeśli wyskoczymy stąd z dołu, wywołaj tę funkcję z tym argumentem”. Coś takiego:
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); }
W tym systemie można wykonać „pełną obsługę wyjątków, jak w C ++”. Ale jest dość chaotyczny i polega na dobrze napisanym kodzie.
źródło
setjmp
ochrony każdej inicjalizacji, a la C ++… i warto wspomnieć, że używanie go do tworzenia wątków jest niestandardowe.Ponieważ wspomniałeś o osadzeniu, myślę, że warto zwrócić uwagę na przypadek nieużywania : kiedy zabrania tego twój standard kodowania. Na przykład MISRA (MISRA-C: 2004: Rule 20.7) i JFS (AV Rule 20): „Makro setjmp i funkcja longjmp nie powinny być używane”.
źródło
setjmp
ilongjmp
może być bardzo przydatne w testowaniu jednostkowym.Załóżmy, że chcemy przetestować następujący moduł:
#include <stdlib.h> int my_div(int x, int y) { if (y==0) exit(2); return x/y; }
Zwykle, jeśli testowana funkcja wywołuje inną funkcję, możesz zadeklarować funkcję pośredniczącą, która będzie naśladować to, co robi rzeczywista funkcja, aby przetestować określone przepływy. W tym przypadku jednak wywołuje funkcję,
exit
która nie zwraca. Kod stub musi w jakiś sposób naśladować to zachowanie.setjmp
ilongjmp
mogę to dla Ciebie zrobić.Aby przetestować tę funkcję, możemy stworzyć następujący program testowy:
#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); }
W tym przykładzie użyjesz
setjmp
przed wejściem do funkcji do testowania, a następnieexit
wywołasz skrót ,longjmp
aby powrócić bezpośrednio do przypadku testowego.Zauważ również, że przedefiniowana
exit
ma specjalną zmienną, którą sprawdza, czy rzeczywiście chcesz wyjść z programu i wywołuje_exit
w tym celu. Jeśli tego nie zrobisz, program testowy może nie zakończyć się poprawnie.źródło
Napisałem Java podobną wyjątkiem mechanizmu manipulacyjną w C z użyciem
setjmp()
,longjmp()
oraz funkcji systemowych. Wyłapuje niestandardowe wyjątki, ale także sygnały takie jakSIGSEGV
. Oferuje nieskończone zagnieżdżanie bloków obsługi wyjątków, które działa na wywołania funkcji i obsługuje dwie najpopularniejsze implementacje wątków. Umożliwia zdefiniowanie hierarchii drzewiastej klas wyjątków, które mają dziedziczenie w czasie łączenia, acatch
instrukcja przechodzi przez to drzewo, aby sprawdzić, czy musi zostać przechwycone lub przekazane dalej.Oto przykład, jak wygląda kod przy użyciu tego:
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("", "")); }
A oto część pliku dołączanego, która zawiera dużo logiki:
#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 */
Istnieje również moduł C, który zawiera logikę do obsługi sygnałów i trochę księgowości.
To było niezwykle trudne do wdrożenia, mogę powiedzieć i prawie zrezygnowałem. Naprawdę starałem się, aby było jak najbliżej Javy; Zaskakujące było, jak daleko zaszedłem mając tylko C.
Daj mi znać, jeśli jesteś zainteresowany.
źródło
main()
wyjdzie z niezłapanego wyjątku . Proszę, zagłosuj na tę odpowiedź :-)Progagation
sekcję w README. Wysłałem swój kod z kwietnia 1999 na GitHub (zobacz link w zredagowanej odpowiedzi). Spójrz; to był twardy orzech do zgryzienia. Byłoby miło usłyszeć, co myślisz.Bez dwóch zdań, najważniejszym zastosowaniem setjmp / longjmp jest to, że działa jako „nielokalny skok goto”. Polecenie Goto (i są rzadkie przypadki, w których będziesz musiał użyć pętli goto over for i while) jest najczęściej używane - bezpiecznie w tym samym zakresie. Jeśli używasz goto do przeskakiwania między zakresami (lub automatycznej alokacji), najprawdopodobniej uszkodzisz stos swojego programu. setjmp / longjmp pozwala tego uniknąć, zapisując informacje o stosie w miejscu, do którego chcesz przejść. Następnie podczas skoku ładuje informacje o stosie. Bez tej funkcji programiści C najprawdopodobniej musieliby zwrócić się do programowania w asemblerze, aby rozwiązać problemy, które może rozwiązać tylko setjmp / longjmp. Dzięki Bogu, że istnieje. Wszystko w bibliotece C jest niezwykle ważne. Dowiesz się, kiedy tego potrzebujesz.
źródło
Oprócz obsługi błędów, inną rzeczą, którą możesz zrobić i której wcześniej nie wspomniano, jest inteligentne zaimplementowanie obliczeń rekurencyjnych ogona w języku C.
W ten sposób są implementowane kontynuacje w C bez konwertowania kodu wejściowego w stylu przekazywania kontynuacji.
źródło