Spis treści
Możemy starannie pisać program, tak by zachowywał się poprawnie w czasie działania. Ale nic z tego nie będzie, jeśli nie uruchomi się poprawnie.
Silnik samochodu, jeśli nie wystartuje, nie jest dobry, nawet jeżeli w czasie działania zachowuje się jak Rolls-Royce.
W przypadku programów komputerowych, najważniejszym jest stwierdzenie, że dane są zainicjowane prawidłowo, co często oznacza właściwą kolejność inicjalizacji.
8.1 Opracowywanie
Zazwyczaj program składa się z kilku pakietów bibliotecznych P, Q, R etc. oraz głównego podprogramu M. Podstawowa zasada jest taka, że po uruchomieniu programu opracowywane są różne pakiety, po czym wywoływany jest podprogram główny. Na opracowanie pakietu składa się tworzenie różnych bytów zadeklarowanych na najwyższym poziomie w pakiecie, lecz nie bytów deklarowanych wewnątrz podprogramów w pakietach, ponieważ tworzone są one w momencie wywołania danego podprogramu.
Ponownie rozważmy pakiet Stack z rozdziału Bezpieczna architektura. W zarysie wyglądał on tak:
package Stack is procedure Clear; procedure Push(X: Float); function Pop return Float; end Stack; package body Stack is Max: constant := 100; Top: Integer range 0 .. Max := 0; A: array (1 .. Max) of Float; ... -- procedury Clear i Push oraz funkcja Pop end Stack;
Opracowanie specyfikacji pakietu nie wnosi nic w tym wypadku, gdyż nie ma zadeklarowanych obiektów. Opracowanie treści pakietu hipotetycznie powoduje przydzielenie pamięci na zmienną całkowitą Top i tablicę A. W tym konkretnym przypadku wielkość tablicy jest znana przed uruchomieniem programu, ponieważ jest ona ustalona stałą Max, mającej wartość statyczną, dzięki czemu wielkość przydzielanej pamięci może być ustalona przed załadowaniem programu.
Lecz stała Max wcale nie musi mieć wartości statycznej. Mogłaby być ona ustalana wynikiem wywołania jakieś funkcji:
Max: constant := Some_Function; Top: Integer range 0 .. Max := 0; A: array (1 .. Max) of Float;
W takim przypadku obszar potrzebny na tablicę A mógłby być obliczany w czasie opracowywania treści pakietu. Gdybyśmy byli nieostrożni i zadeklarowali Max jako zmienną, i zapomnielibyśmy ją zainicjować:
Max: Integer; Top: Integer range 0 .. Max := 0; A: array (1 .. Max) of Float;
to wielkość tablicy byłaby ustalona przez wartość, kórą mogłaby mieć zmienna Max. Jeśli wartość Max byłaby ujemna, to próba zadeklarowania tablicy spowodowałaby zgłoszenie wyjątku Constraint_Error. Jeśli natomiast wartość Max byłaby zbyt duża, zgłoszony by był wyjątek Storage_Error.
Zwróćmy uwagę na to, że zmienna Top ma wartość początkową 0. Dzięki temu użytkownik nie musi przed wywołaniem procedury Push lub Pop wywoływać Clear.
Alternatywnie można w treści pakietu jawnie wprowadzić część inicjalizacyjną. W takim wypadku otrzymamy:
package body Stack is Max: constant := 100; Top: Integer range 0 .. Max; A: array (1 .. Max) of Float; ... -- procedury Clear i Push oraz funkcja Pop begin -- część inicjalizacyjna Top := 0; end Stack;
Część inicjalizacyjna może zawierać zupełnie dowolne instrukcje. Jest ona wykonywana jako część procesu opracowywania treści pakietu, czyli przed wywołaniem jakiegokolwiek z podprogramów w pakiecie, który to podprogram może być wywołany przez kod spoza pakietu.
Czytelnik może uważać, że jest na pewno zawsze najlepiej podawać wartości wszystkich zmiennych. W powyższym przykładzie podanie wartości zerowej jako wartości początkowej jest rzeczywiście sensowne: odpowiada ono wywołaniu procedury Clear. W pewnych sytuacjach brak jest oczywistych wartości początkowych i podawanie ich "na siłę" nie zawsze jest roztropne, ponieważ faktycznie może przesłaniać rzeczywiste błędy. Wrócimy do tego tematu w czasie omawiania języka Spark w ostatnim rozdziale.
W przypadku wartości liczbowych, konsekwencje używania nieustalonych wartości nie mają skutków katastrofalnych. Lecz używanie wartości typu dostępowego lub innych, z których wynika adres, a które nie są wprzód ustalone, mogłoby mieć takie skutki. W przypadku typów dostępowych w Adzie, obiekt tego typu musi mieć albo wartość domyślną null, albo być zainicjowanym.
Podobnego rodzaju błąd dotyczy "dostępu przed opracowaniem". Oznacza to próbę użycia jakiegoś bytu przed jego opracowaniem. Rozważmy:
package P is function F return Integer; X: Integer := F; -- zgłoeszenie wyjątku Program_Error end;
Treść funkcji F znajduje się oczywiście w treści pakietu P. Nie możemy prawidłowo wywołać funkcji F dostarczającej wartości początkowej zmiennej X przed opracowaniem treści. Spowoduje to w tym wypadku zgłoszenie wyjątku Program_Error. Ten sam gatunek błędu w języku C będzie miał nieprzewidywalne skutki.
8.2 Pragmy opracowywania
Wewnątrz jednej jednostki kompilacji panuje zasada, że deklaracje są opracowywane w takiej kolejności w jakiej pojawiają się w treści.
W przypadku, gdy na program składa się kilka różnych jednostek, dana jednostka jest zawsze opracowywana po jednostkach, od których ona sama zależy. A zatem, treść jest opracowywana po odpowiadającej jej specyfikacji, specyfikacja pakietu potomnego - po specyfikacji pakietu macierzystego, a każda jednostka jest opracowywana po wszystkich specyfikacjach wymienionych w klauzuli with (nieograniczonej).
Jednakże powyższe reguły tylko częściowo decydują o kolejności i czasami są one niewystarczające do zapewnienia prawidłowego zachowywania się programu. Rozwińmy powyższy przykład:
package P is function F return Integer; end P; package body P is function F return Integer is ... end P; with P; package Q is X: Integer := P.F; end;
Bardzo ważnym jest by treść pakietu P była opracowana przed specyfikacją pakietu Q, gdyż opracowanie tej z kolei jednostki wymaga wcześniejszego opracowania funkcji F (oraz wszystkich bytów, od których zależna jest jednostka Q). Tyle, że powyższe reguły nie zapewniają takiego zachowania się programu, w efekcie czego możemy się spodziewać zgłoszenia wyjątku Program_Error.
Można wymusić wymaganą kolejność opracowywania poprzez umieszczenie odpowiedniej pragmy w klauzuli kontekstu pakietu jednostki Q:
with P; pragma Elaborate_All (P); package Q is X: Integer := P.F; end;
Słowo All w pragmie Elaborate_All oznacza jej przechodnią naturę. Jej efekt jest taki, że kod opracowujący dla pakietu P (oraz wszystkich pakietów, od których on sam zależy) będzie wykonany przed opracowaniem kodu dla jednostki Q.
Mamy do dyspozycji również pragmę Elaborate_Body, która może być podana ze specyfikacją, a oznacza tyle, że treść musi być opracowana bezpośrednio po specyfikacji.
8.3 Dynamiczne ładowanie
Poniższy podpunkt dotyczy dynamicznego ładowania kodu. Niektóre języki są zaprojektowane tak, by tworzyć pojedyncze, spójne programy w pełni "zmontowane" przed uruchomieniem. Takimi są: Ada, C i Pascal. System operacyjny może przerzucać kawałki programu do i z pamięci wykorzystując algorytm stronicowania, ale to już szczegół implementacyjny.
Inne języki są projektowany, aby zapewnić większą dynamikę. Zezwalają na kompilację nowego kodu, ładowanie go i wykonywanie w czasie działania programu głównego. Takimi są: Java i Cobol.
Podejście wykorzystane w programach pisanych w językach takich jak C jest wykorzystanie dynamicznie ładowanych bibliotek (DDL - Dynamic Linked Libraries). Do wywołania nowego kodu służą wywołania pośrednie. Nie jest to metoda zbyt bezpieczna, gdyż brak tutaj sprawdzania, czy parametry nowego kodu odpowiadają parametrom przekazanym z sekwencji wywołującej "starego" kodu.
Jednym z możliwych rozwiązań do wykorzystania w programach adowych to użycie mechanizmu wywołań dyspozycyjnych jako uchwytu do dynamicznej konsolidacji. Argumentem, który przemawia za tym jest to, że mechanizm dyspozycyjny pozwala istniejącemu skompilowanemu kodowi zawierającemu klasę (taką jak Geometry.Object'Class) wywoływać operacje (takie jak Area) kolejnych typów (takich jak Pentagon, Hexagon etc.) bez konieczności ponownej kompilacji kodu centralnego. Pobieżnie wspomnieliśmy o tym w rozdziale Bezpieczne programowanie obiektowe. Ponadto mechanizm taki jest całkowicie bezpieczny dla systemu typów.
Bardzo dobry przykład rozwiązania praktycznego powyższej idei podany jest w [2].
| Rozdział 7: Bezpieczne zarządzanie pamięcią | Rozdział 9: Bezpieczna komunikacja |
| Tekst oryginalny w języku angielskim - |
|



