Jak zaimplementowano środowisko wykonawcze obsługi wyjątków C ++?

84

Intryguje mnie, jak działa mechanizm obsługi wyjątków w C ++. W szczególności, gdzie jest przechowywany obiekt wyjątku i jak jest on propagowany przez kilka zakresów, dopóki nie zostanie przechwycony? Czy jest przechowywany w jakimś globalnym obszarze?

Ponieważ może to być specyficzne dla kompilatora, czy ktoś mógłby to wyjaśnić w kontekście zestawu kompilatorów g ++?

Paul Joseph
źródło
4
Przeczytaj ten artykuł pomoże ci
Ahmed powiedział
Nie wiem - ale zgaduję, że specyfikacja C ++ ma jasną definicję. (Chociaż mogę się mylić)
Paul Nathan
2
Nie, specyfikacja nie podaje definicji. Dyktuje zachowanie, a nie implementację. Paul, możesz chcieć sprecyzować, która implementacja Cię interesuje.
Rob Kennedy
1
Powiązane pytanie: stackoverflow.com/questions/307610/…
CesarB
Zapoznaj się z sekcją 5.4 raportu technicznego na temat wydajności C ++
Yogesh Arora

Odpowiedzi:

49

Implementacje mogą się różnić, ale istnieje kilka podstawowych pomysłów wynikających z wymagań.

Sam obiekt wyjątku jest obiektem utworzonym w jednej funkcji, zniszczonym w funkcji wywołującej. W związku z tym zwykle nie jest możliwe utworzenie obiektu na stosie. Z drugiej strony wiele obiektów wyjątków nie jest zbyt dużych. Ergo, można utworzyć np. 32-bajtowy bufor i przepełnić stertę, jeśli faktycznie potrzebny jest większy obiekt wyjątku.

Jeśli chodzi o faktyczne przeniesienie kontroli, istnieją dwie strategie. Jednym z nich jest zapisanie wystarczającej ilości informacji w samym stosie, aby rozwinąć stos. Jest to w zasadzie lista destruktorów do uruchomienia i programów obsługi wyjątków, które mogą przechwytywać wyjątek. Kiedy zdarzy się wyjątek, cofnij stos wykonujący te destruktory, aż znajdziesz pasujący zaczep.

Druga strategia przenosi te informacje do tabel poza stosem. Teraz, gdy wystąpi wyjątek, stos wywołań jest używany do ustalenia, które zakresy są wprowadzane, ale nie wychodzą. Są one następnie wyszukiwane w tabelach statycznych, aby określić, gdzie zostanie obsłużony zgłoszony wyjątek i które destruktory działają między nimi. Oznacza to, że na stosie jest mniej wyjątków; i tak potrzebne są adresy zwrotne. Tabele to dodatkowe dane, ale kompilator może umieścić je w segmencie programu na żądanie.

MSalters
źródło
4
AFAIR g ++ wykorzystuje drugie podejście, tablicę adresów, prawdopodobnie ze względu na zgodność z C. Kompilator C ++ firmy Microsoft stosuje podejście łączone, ponieważ jego wyjątki C ++ są zbudowane na podstawie SEH (strukturalnej obsługi wyjątków). W każdej funkcji C ++ MSC ++ tworzy i rejestruje rekord obsługi wyjątków SEH, który wskazuje na tabelę z zakresami adresów bloków try-catch i destruktorów w tej konkretnej funkcji. rzuca pakietom wyjątek C ++ jako wyjątek SEH i wywołuje RaiseException (), a następnie SEH zwraca kontrolę do procedury obsługi specyficznej dla C ++.
Anton Tykhyy
1
@Anton: tak, używa metody tablicy adresów. Zobacz moją odpowiedź na inne pytanie na stackoverflow.com/questions/307610/…, aby uzyskać szczegółowe informacje.
CesarB
Dziękuję za odpowiedź. Możesz zobaczyć, jak puryści C mogą być przerażeni C ++ i jego wyjątkami. Pomysł, że proste try / catch może nieświadomie utworzyć wiele obiektów stosu w czasie wykonywania lub nadmuchać program dodatkowymi tabelami, jest powodem, dla którego systemy wbudowane często ich unikają.
szybowiec
@speedplane: Nie, to bardziej z powodu braku zrozumienia. Obsługa błędów nigdy nie jest bezpłatna. C po prostu zmusza cię do napisania tego samodzielnie. Wszyscy wiemy, ilu programom C brakuje jednego free()lub drugiego fclose()w jakiejś rzadko używanej ścieżce kodu.
MSalters
@MSalters Nie zgadzam się, to prawie całkowity brak zrozumienia. Inżynierowie często nie rozumieją, jak działają wyjątki i jak wyjątki wpłyną na ich kod, co słusznie prowadzi do wahania podczas korzystania z wyjątków. Jeśli implementacja obsługi wyjątków byłaby wyraźniej zakomunikowana (i nie wydawałaby się magią), wielu byłoby mniej niechętnych przed ich użyciem.
szybowiec
20

Jest to zdefiniowane w 15.1 Zgłaszanie wyjątku od standardu.

Rzut tworzy tymczasowy obiekt.
Sposób przydzielania pamięci dla tego tymczasowego obiektu nie jest określony.

Po utworzeniu tymczasowego obiektu kontrola jest przekazywana do najbliższego programu obsługi w stosie wywołań. rozwijanie stosu między punktem wyrzutu a punktem zaczepienia. Gdy stos jest rozwijany, wszystkie zmienne stosu są niszczone w odwrotnej kolejności tworzenia.

O ile wyjątek nie zostanie ponownie wyrzucony, przedmiot tymczasowy jest niszczony na końcu przewodnika, na którym został złapany.

Uwaga: Jeśli złapiesz przez odniesienie, odniesienie będzie odnosić się do tymczasowego, Jeśli złapiesz według wartości, obiekt tymczasowy jest kopiowany do wartości (i dlatego wymaga konstruktora kopiującego).

Porada S.Meyersa (Catch by const reference).

try
{
    // do stuff
}
catch(MyException const& x)
{
}
catch(std::exception const& x)
{
}
Martin York
źródło
3
Inną nieokreśloną rzeczą jest to, w jaki sposób program rozwija stos i skąd program wie, gdzie jest „najbliższa procedura obsługi”. Jestem prawie pewien, że Borland posiada patent na jedną metodę realizacji tego.
Rob Kennedy
Dopóki obiekty są niszczone w odwrotnej kolejności do tworzenia, szczegóły implementacji nie są ważne, chyba że jesteś inżynierem kompilatora.
Martin York,
1
Odebrano: a) „Scott Meyers”, a nie „S. Myers”; b) nieprawdziwe cytowanie: „Efektywny C ++”: „Pozycja 13: Wyłap wyjątki przez odniesienie. ”. Umożliwi to modyfikowanie / dołączanie informacji do obiektu wyjątku.
Sebastian Mach
3
@phresnel: Nie zapomnij o pozycji 21: „Używaj const, gdy tylko jest to możliwe”. Nie ma dobrych argumentów za poprawieniem wyjątku. Powinieneś być a) „naprawiać i odrzucać”, b) ponownie rzucać lub c) generować nowy wyjątek.
Martin York
1
@phresnel: Tak, masz swoje powody (nie zgadzasz się z twoją logiką), ja mam swoje i chociaż nie twierdzę, że rozmawiałem z nimi na ten konkretny temat ani nie znam ich umysłów (Meyers, Alexandrescu i Sutter). moja interpretacja jest ważna. Ale jeśli jesteś w okolicy Seattle, możesz porozmawiać z wszystkimi trzema, ponieważ są oni regularnymi uczestnikami grupy użytkowników North West C ++ (Meyers rzadziej niż pozostali).
Martin York,
13

Możesz zajrzeć tutaj, aby uzyskać szczegółowe wyjaśnienie.

Pomocne może być również przyjrzenie się sztuczce używanej w zwykłym C, aby zaimplementować podstawowy rodzaj obsługi wyjątków. Pociąga to za sobą użycie setjmp () i longjmp () w następujący sposób: pierwsza zapisuje stos w celu oznaczenia procedury obsługi wyjątków (np. „Catch”), podczas gdy druga jest używana do „rzucania” wartości. Wartość „wyrzucona” jest widziana tak, jakby została zwrócona przez wywołaną funkcję. Blokada „try” kończy się po ponownym wywołaniu setjmp () lub po powrocie funkcji.

Eduard - Gabriel Munteanu
źródło