jcp, PLIK12


Rozdział 12

Wyjątki

Bł~dy czasu wykonania 12 ~ 1 Program źródłowy przygotowany w języku C++

(pliki z rozszerzeniem .cpp) jest przetwarzany przez system programowania na równoważny program zapi­sany w języku wewnętrznym komputera. Przekształcenie to zachodzi w dwóch etapach. Najpierw kompilator analizuje program źródłowy zgłaszając wszystkie wykryte błędy leksykalne i składniowe. Są to blędy czasze kompilacji - programi­sta może je usunąć za pomocą edytora tekstów będącego najczęściej składnikiem systemu programowania. Poprawny leksykalnie i składniowo program źródłowy jest przez kompilator przekształcany na równoważny program półskompilowany

j zawarty w plikach o rozszerzeniu .obj. Pliki te są następnie przetwarzane przez program łączący, który dodaje zadeklarowane biblioteki funkcji i klas (zawarte również w plikach .obj). Rezultatem pracy programu łączącego jest gotowy do wykonania przekład wyjściowego programu źródłowego zapisany w pliku o roz­szerzeniu .exe. Przekład ten jest następnie wykonywany pod nadzorem systemu operacyjnego.

Podczas wykonania przekładu programu mogą wystąpić blędy czasu ~~ykoha­iaia. Błędy te można podzielić na dwie grupy

!i ,i !j

12. 1. Błędy czasu wykonania 2,G~1

błędy wykrywane sprzętowo - na przykład przekroczenie zakresu liczb zmiennopozycyjnych czy próba odwołania do miejsca pamięci niedostępnego dla wykonywanego programu,

błędy wykrywane przez oprogramowanie - na przykład przekroczenie zakre­su tablicy czy niewłaściwe argumenty przekazywane funkcjom (szczególnie funkcjom bibliotecznym).

Wystąpienie błędu wykrywanego sprzętowo powoduje najczęściej przerwanie wykonywania programu i wyprowadzenie standardowej sygnalizacji systemu operacyjnego opisującej rodzaj błędu. Błędy wykrywane przez oprogramowanie są najczęściej spowodowane przekazywaniem niewłaściwych argumentów funk­C~onl. Typowy przykład to przekazanie funkcji bibliotecznej realizującej otwarcie pliku dyskowego w trybie odczytu nazwy pliku, który nie został jeszcze utwo­rzony.

Rozważmy możliwe sposoby reakcji funkcji na błędne argumenty przekazane w jej wywołaniu. Po pierwsze funkcja może zasygnalizować błąd za pomocą wy­różnionej wartości swego wyniku. Często jest to wartość 0 lub wartość -1. Nie zawsze jednak metoda ta daje się zastosować, bowiem niekiedy wartości te mogą być również poprawnymi wynikami funkcji. Druga możliwość sygnalizowania błędów to rozszerzenie listy argumentów funkcji. Do podstawowych argumentów można dodać jeszcze jeden argument typu wskaźnikowego. Może to być na przy­kład wskaźnik zmiennej typu chat zadeklarowanej w programie źródłowym. Po zakończeniu wykonywania funkcji wartość tej zmiennej sygnalizuje poprawne lub błędne wykonanie (np. wartość 0 - poprawnie, wartość większa od zera ­numer błędu). Wskaźnik będący dodatkowym arg~nnentem funkcji może też być wskaźnikiem funkcji obsługi błędu, zawartej w programie źródłowym, która ma zostać wywołana w przypadku wykrycia błędu. Taka metoda sygnalizowania błędów czasu wykonania jest bardzo efektywna, ale wymaga od programisty sto­sowania dosyć skomplikowanych konstrukcji. Jeszcze inne proste rozwiązanie problemu sygnalizowania błędów wykrywanych szczególnie podczas wykonywa­nia funkcji bibliotecznych to wprowadzenie w danej bibliotece funkcji globalnej zmiennej sygnalizującej błędy. Każdorazowo po wykonaniu jednej z funkcji bi­bliotecznych należy sprawdzić, czy zmienna ta nie sygnalizuje wystąpienia błędu.

Przekazywanie informacji o wykryciu błędu za pomocą zmiennej wymaga każdorazowo po wywołaniu funkcji wstawienia do programu wywołującego in­strukcji testujących wartość tej zmiennej. Instrukcje te zwiększają objętość pro­gramu i czas jego wykonania. Konsekwentne stosowanie tej metody wymaga od programisty dużej dyscypliny i jest szczególnie niewygodne, gdy na przykład funkcja jest wywoływana wielokrotnie w złożonym wyrażeniu arytmetycznym.

0x01 graphic

242 Ro=dział 12. Wvjqtki

Z przedstawionych rozwiązań najbardziej efektywna jest metoda przekazywa­nia w wywołaniu funkcji wskaźnika dodatkowej funkcji obsługi błędu. Funkcja obsługi jest bowiem wykonywana tylko wtedy, gdy jest to konieczne, a dodatko­WO Unlkallly wielokrotnego wpisywania instrukcji testujących wystąpienie błędu. Przedstawiona w następnym punkcie metoda obsługi wyjątków, dostępna w języ­ku C++, jest równoważna metodzie przekazywania wskaźnika funkcji obsługi

błędu a z drugiej strony jest znacznie prostsza w zapisie. ;

Obsługa wyjątków 12 ~ f~ Mechanizm obs~ztgi wyjcitków składa się z trzech części:

zgłoszenia wyjątku, dokonywanego po wykryciu błędu, sygnalizacji wywołania funkcji mogącej zgłosić wyjątek, sekcji obsługi wyjątku.

Zgłoszenie wyjątku następuje w definicji funkcji wywoływanej, pozostałe ;,,

i dwie części obsługi wyjątków należy umieścić w funkcji wywołującej.

Instrukcja zgłoszenia wyjątku ma postać:

throw wyrażenie ;

Wyrażenie występujące po słowie kluczowym throw umożliwia sygnalizowanie typu wyjątku. Wartością tego wyrażenia może być wartość dowolnego typu lub obiekt pewnej klasy.

throw 12 ; // wyjątek typu int throw 1.5 ; // wyjątek typu float throw "Błąd" ; // wyjątek typu char* //

class OpisBłędu ;

throw OpisBłędu ( ) ; // wyjatek typu OpisBłędu

12.2. Obsluga wyjqt/ców 243

Wyrażenie określające typ wyjątku może nie występować i wówczas instrukcja zgłoszenia wyjątku sprowadza się do postaci

throw ;

W jednej funkcji można zgłaszać wiele wyjątków różnych typów. float Dzielenie ( float flDzielna, float flDzielnik )

f

if ( flDzielnik != 0 )

return flDzielna / flDzielnik ; else

f throw flDzielnik ; // typ float return 0 ;

// class BrakOdbioru f ?~

class BłądKomunikatu f

public: chat m cSumaKontrolna ; chat m cNagłówek ;

int m_nLiczbaZnaków ;

BłądKomunikatu ( chat cSuma, chat cNagłówek, int nDługość) ( m cSumaKontrolna ) cSuma,

( m cNagłówek ) cNagłówek, (m_nLiczbaZnaków ) nDługc~ć { }

0x01 graphic

244 Rozdział l2. Wyjątki

'J void OdbierzKomunikat ( char` pcBufor ) {

// oczekiwanie na przybycie komunikatu

if ( CzasMinąłKomunikatuNieOdebrano ( ) ) throw BrakOdbioru ( ) ;

else {

// analiza komunikatu

if ( KomunikatPoprawny ( ) ) return ;

else {

BłądKomunikatu *pOpisBłędu = new OpisBłędu

( TestSumyKontrolnej ( ), TestNagłówka ( ), !' Długc~ćKomunikatu ( ) )

throw pOpisBłędu ; return ;

Funkcja OdbierzKomunikat korzysta z dwóch klas określających typ wyjątku. Klasa BrakOdbioru nie zawiera żadnych składowych - jej pusty obiekt sygna­lizuje jedynie rodzaj błędu. Natomiast klasa BłądKomunikatu zawiera trzy skła­dowe, które opisują rodzaj błędu - po utworzeniu obiekt tej klasy jest przekazy­wany jako wartość wyrażenia w instrukcji throw.

Sygnalizacja wywołania funkcji, która może zgłosić wyjątek ma postać: try { blok }

gdzie blok jest ciągiem deklaracji i instrukcji.

12.2. Obsłztga wyjqfków 245

try {

NiebezpiecznaFunkcja ( ) ; }

ll try {

float flWanośćTransakcji, flCenaJednejSztuki ; int nLiczbaSztuk ;

char acBufor [ 1024 J ; OdbierzKomunikat ( acBufor ) ;

l/ przepisz wartość transakcji i liczbę sztuk do odpowiednich zmiennych flCenaJednejSztuki = Dzielenie ( flWartośćTransakcji, flLiczbaSztuk ) ; }

Sekcja obsługi ma następująca postać: catch ( deklaracją wyjcttku ) { blok }

Sekcja ta rozpoczynająca się od słowa kluczowego catch, może wystąpić jedynie bezpośrednio po zgłoszeniu wywołania funkcji mogącej powodować wyjątek lub po innej sekcji obsługi wyjątku.

try catch (...)

Il

0x01 graphic

i

2G~6 Rozdział l2. Wyjątki

try catch ( typ, ) catch ( typ2 )

catch ( typ„ )

W nawiasach okrągłych występujących za słowem kluczowym catch wpisywana jest deklaracja uryjdtku, która może być:

(a) trójkropkiem ... i

(b) identyfikatorem dowolnego typu liczbowego lub identyfikatorem klasy (c) argumentem o postaci

' identyfckator_typu ideny ckator argumentu identyfikator klasy identyfikator obiektcc

I W przypadku (a) blok zawarty w sekcji obsługi wyjątków jest wykonywany po wystąpieniu dowolnego wyjątku z bloku sygnalizacyjnego poprzedzającego tę sekcję. Trójkropek służy więc do przechwytywania wszystkich zgłoszonych wy­jątków niezależnie od ich typu.

double KwadratElementu ( double adbTablica [ ], int nRozmiar, int nlndeks ) I, {

', if ( nlndeks >= nRozmiar )

throw nlndeks ; // typ int I return 0;

','; I )

12.2. Obsltsga wyjątków 247

if ( adbTablica [ nlndeks ] > MAKSYMALNY_DOPUSZCZALNY ) i

throw anTablica [ nlndeks ] ; // typ double return 0 ;

return adbTablica [ nlndeks ] * adbTablica [ nlndeks ] ;

i

// try i

KwadratElementu ( adbWektor, nDługość, nPozycja ); // następne instrukcje bloku

i

catch (...) i

cout « "\n Oj błąd!" ;

]

// następne instrukcje programu

Każdy z wyjątków zgłoszonych przez funkcję KwadratElementu zostanie obsłu­żony przez jedyną sekcję obsługi związaną z tym wywołaniem. Niezależnie od tego, czy wyjątek wystąpił i został obsłużony, czy też żaden z wyjątków nie został zgłoszony sterowanie przechodzi do następnych instrukcji bloku, a gdy ich już uie ma do następnych instrukcji programu.

Gdy deklaracja wyjątku jest identyfikatorem typu lub identyfikatorem klasy (przypadek (b)) wyjątki są rozróżniane z dokładnością do typu - nie są natomiast przekazywane wartości wyrażeń występujących w zgłoszeniu wyjątku.

try KwadratElementu ( adbWektor, nDługość, nPozycja );

0x01 graphic

248 Rosc~iałl2. Wyjątki

catch ( int )

cout « "\n Zbyt duży indeks." ;

)

catch ( double ) i

cout « "\n Zbyt duża wartość elementu." ;

Najbardziej dokładną informację o rodzaju błędu, którego wykrycie powoduje zgłoszenie wyjątku można uzyskać stosując jako deklarację wyjątku argument zawierający określenie typu lub klasy oraz identyfikator argumentu formalnego (wersja (c)).

try KwadratElementu ( anWektor, nDługość, nPozycja ); )

catch ( int nlndeks )

cout « "\n Indeks o wart~ci "

« nlndeks « " przekracza rozmiar wektora. " ;

i

catch ( double dbElement ) i

cout « "\n Podniesienie warta5ci " « dbElement

« " do kwadratu spowoduje przekroczenie zakresu liczb typu double. "

//

12.2. Obsfhga wyjqtków 249

try f

OdbierzKomunikat ( acBufor ) ; i

catch ( BrakOdbioru ) f

cout « "1n Upłyrx~ł zadany czas oczekiwania na odebranie komunikatu." ;

) catch ( BłądKomunikatu *pOpisBłędu ) f

cout « "1n Odebrano bidny komunikat o długości " « pOpisBłędu -> m nLiczbaZnaków ;

if ( pppisBłędu -> m cSumaKontrolna )

cout « "1n Błędna suma kontrolna komunikatu." ; if ( pOpisB~du -> m cNagłowek )

cout « "\n Błędny nagłówek komunikatu." ; delete pOpis~du ;

i

W podanych przykładach obsługa wyjątku sprowadzała się jedynie do wy­świetlenia odpowiedniego tekstu. Najczęściej reakcja taka jest niewystarczająca. Nie można bowiem kontynuować obliczeh, gdy na przykład wykryta została pró­ba odwołania do nieistniejącego elementu tablicy. W takich przypadkach wyko­nanie programu powinno zostać zakończone. Niekiedy możliwe jest zwrócenie się do użytkownika z prośbą o skorygowanie błędnych wartości, ale na poprawną odpowiedź można jedynie liczyć, gdy użytkownikiem jest autor programu.

0x01 graphic

Rozdzia~ 12. Wyjątki

Hierarchia wyjątków 12.3

Rozważmy ciąg wywołań fiu~kcji FunkcjaPierwsza ~ FunkcjaDruga ~ FunkcjaTrzecia ~ FunkcjaCzwarta

Przyjmijmy, że wywołanie każdej z tych funkcji zawarte jest w bloku rozpoczy­nającym się od słowa kluczowego try, czyli jest realizowane w ramach sygnaliza­cji wywołania funkcji, która może zgłosić wyjatek. Gdy FunkcjaCzwarta zgłosi wyjątek pewnego typu, to może on zostać obsłużony w FunkcjiTrzeciej, o ile za­i

~~" wiera ona sekcję obsługi wyjątków odpowiedniego typu. Jeżeli takiej sekcji nie ma w FunkcjiTrzeciej, to wyjątek jest przekazywany do obsługi w FunkcjiDrugiej. W ten sposób wyjątek zgłoszony na pewnym poziomie zagnieżdżenia wywołań funkcji jest przekazywany kolejno aż do pierwotnej funkcji wywołującej. Gdy i ta funkcja nie zawiera sekcji obsługi wyjątku danego typu, to najczęściej wyko­nanie programu jest przerywane i wyświetlany jest stosowny komunikat syste­mowy.

enum RodzajeWyjątków { PIERWSZY, DRUGI, TRZECI } ; ', void FunkcjaCzwarta ( )

i

throw 1 ; // typ int

throw DRUGI ; // typ RodzajeWyjątków

(, void FunkcjaTrzecia ( )

'' try FunkcjaCzwarta ( ) ; } i

12.3. Kierarchia

catch ( int ) // obsługa wyjątku typu int

throw 'A' ; // typ char

}

void FunkcjaDruga ( )

try {

FunkcjaTrzecia ( ) ;

} catch ( RodzajeWyjątków ) // obsługa wyjątku typu RodzajeWyjątków

} void FunkcjaPierwsza ( ) {

try {

FunkcjaDruga ( ) ; }

catch (...) // obsługa wszystkich dotąd nieobsłużonych wyjątków

251

0x01 graphic



Wyszukiwarka