Myślałem dzisiaj o blokach try / catch istniejących w innych językach. Przez chwilę googlowałem, ale bez rezultatu. Z tego, co wiem, nie ma czegoś takiego jak try / catch w C. Czy jest jednak sposób, aby je „zasymulować”?
Jasne, jest asercja i inne sztuczki, ale nic takiego jak try / catch, które również łapią podniesiony wyjątek. Dziękuję Ci
101
Odpowiedzi:
Samo C nie obsługuje wyjątków, ale można je do pewnego stopnia symulować za pomocą
setjmp
ilongjmp
wywołań.Ta strona internetowa zawiera fajny poradnik, jak symulować wyjątki za pomocą
setjmp
ilongjmp
źródło
try{ x = 7 / 0; } catch(divideByZeroException) {print('divided by zero')};
to nie zadziała, prawda?Używasz goto w C w podobnych sytuacjach obsługi błędów.
To najbliższy odpowiednik wyjątków, jaki można uzyskać w C.
źródło
goto
jest bardziej używany do obsługi błędów, ale co z tego? Pytanie nie dotyczy obsługi błędów jako takiej, ale wyraźnie dotyczy odpowiedników try / catch.goto
nie jest odpowiednikiem try / catch, ponieważ jest ograniczone do tej samej funkcji.goto
jako mechanizmu try / catch używanego w nowoczesnym, szeroko akceptowanym, recenzowanym źródle. Wyszukajgoto
odpowiednik „rzut” ifinish
odpowiednik „złapania”.Ok, nie mogłem się powstrzymać od odpowiedzi na to. Pozwólcie, że najpierw powiem, że nie uważam, aby symulowanie tego w C nie było dobrym pomysłem, ponieważ jest to naprawdę obca koncepcja dla C.
Możemy
użyćnadużyć preprocesora i lokalnych zmiennych stosu, aby dać użycie ograniczonej wersji C ++ try / throw / catch.Wersja 1 (rzuty zakresu lokalnego)
Wersja 1 jest tylko rzutem lokalnym (nie można opuścić zakresu funkcji). Opiera się na zdolności C99 do deklarowania zmiennych w kodzie (powinno działać w C89, jeśli próba jest pierwszą rzeczą w funkcji).
Ta funkcja po prostu tworzy lokalną zmienną, więc wie, czy wystąpił błąd i używa goto, aby przeskoczyć do bloku catch.
Na przykład:
To działa na przykład:
Wersja 2 (skoki przez lunetę)
Wersja 2 jest dużo bardziej złożona, ale zasadniczo działa w ten sam sposób. Wykorzystuje długi skok z bieżącej funkcji do bloku try. Blok try używa następnie if / else, aby przeskoczyć blok kodu do bloku catch, który sprawdza zmienną lokalną, aby zobaczyć, czy powinna przechwycić.
Przykład ponownie się rozwinął:
Używa globalnego wskaźnika, więc longjmp () wie, jaka próba została ostatnio uruchomiona. Jesteśmy
przy użyciunadużywa stos tak funkcjonuje dziecko może mieć również blok try / catch.Korzystanie z tego kodu ma wiele wad (ale jest fajnym ćwiczeniem umysłowym):
źródło
bool __ErrorCheck(bool &e){bool _e = e;e=false;return _e;}
. Ale zmienna lokalna również zostałaby ponownie zdefiniowana, więc sprawy wymykają się spod kontroli.W C99możesz użyćsetjmp
/longjmp
dla nielokalnego przepływu sterowania.W ramach jednego zakresu ogólny, ustrukturyzowany wzorzec kodowania dla języka C w obecności wielu alokacji zasobów i wielu wyjść
goto
, jak w tym przykładzie . Jest to podobne do tego, jak C ++ implementuje wywołania destruktorów automatycznych obiektów pod maską i jeśli będziesz się tego pilnie trzymał, powinno to pozwolić na pewien stopień czystości nawet w złożonych funkcjach.źródło
Chociaż niektóre z pozostałych odpowiedzi objęły proste przypadki korzystania
setjmp
ilongjmp
, w rzeczywistej aplikacji nie ma obawy, że dwa naprawdę sprawa.jmp_buf
spowoduje, że nie będą one działać.jmp_buf
spowoduje w tej sytuacji wszelkiego rodzaju ból.Rozwiązaniem tego problemu jest utrzymanie lokalnego stosu wątków
jmp_buf
który jest aktualizowany na bieżąco. (Myślę, że właśnie tego używa lua wewnętrznie).Więc zamiast tego (z niesamowitej odpowiedzi JaredPara)
Użyłbyś czegoś takiego:
Ponownie, bardziej realistyczna wersja tego zawierałaby sposób na przechowywanie informacji o błędach w pliku
exception_state
, lepsza obsługaMAX_EXCEPTION_DEPTH
pliku (może użycie realloc w celu powiększenia bufora lub coś w tym rodzaju).ZRZECZENIE SIĘ: Powyższy kod został napisany bez jakichkolwiek testów. Jest to wyłącznie po to, abyś miał pojęcie, jak uporządkować rzeczy. Różne systemy i różne kompilatory będą musiały inaczej implementować pamięć lokalną wątku. Kod prawdopodobnie zawiera zarówno błędy kompilacji, jak i błędy logiczne - więc jeśli możesz go używać według własnego uznania, PRZETESTUJ go przed użyciem;)
źródło
Szybkie wyszukiwanie w Google daje niezłe rozwiązania, takie jak to które używają setjmp / longjmp, jak wspominali inni. Nie ma nic tak prostego i eleganckiego jak try / catch w C ++ / Javie. Jestem raczej stronniczy w kwestii obsługi wyjątków przez Adę.
Sprawdź wszystko za pomocą instrukcji if :)
źródło
Można to zrobić
setjmp/longjmp
w C. P99 ma do tego całkiem wygodny zestaw narzędzi, który jest również zgodny z nowym modelem gwintu C11.źródło
To kolejny sposób obsługi błędów w C, który jest bardziej wydajny niż użycie setjmp / longjmp. Niestety, nie będzie działać z MSVC, ale jeśli używasz tylko GCC / Clang, możesz to rozważyć. W szczególności używa rozszerzenia „etykieta jako wartość”, które umożliwia pobranie adresu etykiety, zapisanie jej w wartości i bezwarunkowy przeskok do niej. Przedstawię to na przykładzie:
Jeśli chcesz, możesz refaktoryzować wspólny kod w definicjach, skutecznie wdrażając własny system obsługi błędów.
Wtedy staje się przykład
źródło
Ostrzeżenie: poniższe nie są zbyt miłe, ale spełniają swoje zadanie.
Stosowanie:
Wynik:
Pamiętaj, że jest to użycie zagnieżdżonych funkcji i
__COUNTER__
. Będziesz po bezpiecznej stronie, jeśli używasz gcc.źródło
Redis używa goto do symulacji try / catch, IMHO jest bardzo czysty i elegancki:
źródło
errno
należy używać tylko po nieudanym wywołaniu systemowym, a nie trzy wywołania później.W C można „symulować” wyjątki wraz z automatycznym „odzyskiwaniem obiektów” poprzez ręczne użycie if + goto do jawnej obsługi błędów.
Często piszę kod w C, jak poniżej (sprowadzając się do obsługi błędów):
Jest to całkowicie standardowe ANSI C, oddziela obsługę błędów od głównego kodu, umożliwia (ręczne) rozwijanie stosu zainicjowanych obiektów, podobnie jak C ++, i jest całkowicie oczywiste, co się tutaj dzieje. Ponieważ w każdym momencie jawnie testujesz błąd, ułatwia to wstawianie określonego rejestrowania lub obsługi błędów w każdym miejscu, w którym może wystąpić błąd.
Jeśli nie masz nic przeciwko odrobinie magii makr, możesz uczynić to bardziej zwięzłym, wykonując inne czynności, takie jak rejestrowanie błędów ze śladami stosu. Na przykład:
Oczywiście nie jest to tak eleganckie, jak wyjątki C ++ + destruktory. Na przykład zagnieżdżanie wielu stosów obsługi błędów w jednej funkcji w ten sposób nie jest zbyt czyste. Zamiast tego prawdopodobnie chciałbyś podzielić je na niezależne funkcje podrzędne, które podobnie obsługują błędy, inicjalizuj + finalizuj jawnie w ten sposób.
Działa to również tylko w ramach pojedynczej funkcji i nie będzie przeskakiwać w górę stosu, chyba że wywołania wyższego poziomu zaimplementują podobną jawną logikę obsługi błędów, podczas gdy wyjątek C ++ będzie po prostu przeskakiwał stos, dopóki nie znajdzie odpowiedniego programu obsługi. Nie pozwala też na wyrzucenie dowolnego typu, ale zamiast tego tylko kod błędu.
Systematyczne kodowanie w ten sposób (tj. - z pojedynczym wejściem i pojedynczym punktem wyjścia) również bardzo ułatwia wstawianie logiki pre i post („ostatecznie”), która będzie wykonywana bez względu na wszystko. Po etykiecie END umieszczasz swoją logikę „ostatecznie”.
źródło
Jeśli używasz C z Win32, możesz wykorzystać jego Structured Exception Handling (SEH) do symulacji try / catch.
Jeśli używasz C na platformach, które nie obsługują
setjmp()
ilongjmp()
, spójrz na tę obsługę wyjątków biblioteki pjsip, zapewnia ona własną implementacjęźródło
Być może nie jest to główny język (niestety), ale w APL istnieje operacja ⎕EA (skrót od Execute Alternate).
Sposób użycia: „Y” ⎕EA „X”, gdzie X i Y to fragmenty kodu dostarczane jako ciągi znaków lub nazwy funkcji.
Jeśli X napotka błąd, zamiast tego zostanie wykonana Y (zwykle obsługa błędów).
źródło