Metody realizacji języków programowania/MRJP Wykład 10
Wyjątki
- Pojęcie wyjątek oznacza błąd (nietypową, niepożądaną
sytuację).
- Obsługa wyjątków oznacza reakcję programu na wykryte błędy.
- Funkcja, która napotkała problem zgłasza (rzuca)
wyjątek.
- Wyjątek jest przekazywany do miejsca wywołania funkcji, gdzie
może być wyłapany i obsłużony albo przekazany wyżej. Innymi słowy poszukiwania bloku obsługi wyjątku dokonywane są połańcuchu DL.
- Przy wychodzeniu z funkcji i bloków usuwane są obiekty automatyczne.
Składnia
Dla ustalenia uwagi przyjmiemy składnię C++ (składnia Javy jest w tej kwestii bardzo podobna)
Zgłoszenie wyjątku
throw <wyrażenie>
Obsługa wyjątków
try { <instrukcje> } catch(<parametr 1>) { <obsługa wyjątku 1> //... } catch(<parametr n>) { <obsługa wyjątku n> }
Semantyka
- Gdy któraś z instrukcji w części try przekazała
wyjątek, przerywamy wykonanie tego ciągu i szukamy catch z odpowiednim parametrem.
- Jeśli znajdziemy, to wykonujemy obsługę tego wyjątku, a po jej
zakończeniu instrukcje po wszystkich blokach catch.
- Jeśli nie znajdziemy, przechodzimy do miejsca wywołania
(usuwając obiekty automatyczne bieżącej funkcji) i kontynuujemy poszukiwanie.
- Jeśli nie znajdziemy w żadnej z aktywnych funkcji, wykonanie
programu zostanie przerwane.
Implementacja obsługi wyjątków
Obsługę wyjątków można zrealizować na wiele różnych sposobów. Bardzo istotne jest jednak to, aby narzut (dodatkowy czas wykonania programu i zużyta pamięć) przy normalnym (tj. bez wystąpienia wyjątków) wykonaniu programu był minimalny, a w miarę możności zerowy. Z tego punktu widzenia realizacje wymagające wykonania dodatkowych czynności na początku i końcu bloku try można uznać za nieefektywne.
Bliższe spojrzenie na semantykę wyjątków, ujawnia, że w momencie zgłoszenia wyjątku muszą zostać wykonane następujące czynności:
- stwierdzenie, czy nastąpiło ono wewnątrz bloku try
- identyfikacja aktywnych bloków try - moze być więcej niż jeden, np.
h() { try { f(); try { g(); // tu wyjątek } catch E1 { ... } } catch E2 { ... } }
- rozpoznanie typu zgłoszonego wyjątku
- próba dopasowania do typu wyjątku jednego z bloków catch
- w wypadku powodzenia wykonanie tego bloku
- w przeciwnym wypadku przekazanie wyjątku w górę DL
Identyfikacja aktywnych bloków try
Dla identyfikacji aktywnych bloków try, w czasie wykonania programu musi być dostępna informacja (struktura danych), która dla każdej instrukcji pozwoli ustalić czy i jakie bloki try ją otaczają. Jeżeli chcemy uniknąć narzutu dla 'prawidłowego' przebiegu programu, informacja taka musi być w całości wygenerowana w czasie kompilacji.
Prostą a efektywną metodą spełniającą te wymagania jest użycia tablicy indeksowanej adresami instrukcji (a raczej zakresami adresów, w przeciwnym bowiem przypadku tablica miałaby rozmiar zbliżony do całkowitej liczby instrukcji programu). Elementami tej tablicy będą listy odpowiednich bloków try, lub listy odpowiednich bloków catch
Przykład
Rozważmy przytoczony wcześniej przykład funkcji:
h() { try { f(); try { g(); // tu wyjątek } catch E1 { I1; } } catch E2 { I2; } I3; }
gdzie I1, I2,I3 są instrukcjami niezgłaszającymi wyjątków
Załóżmy przy tym, że wygenerowany dla niej został następujący kod maszynowy:
0: enter 1: call f 2: call g 3: jmp 7 4: I1 5: jmp 7 6: I2 7: I3 8: leave 9: ret
Tablica, o której mowa wyglądać będzie następująco
Od | Do | Bloki catch |
---|---|---|
1 | 1 | C1 |
2 | 2 | C1, C2 |
3 | 6 | C1 |