C dla każdego (cz. 1.)
Wstęp
Język C to dzisiaj jeden z najpopularniejszych
języków programowania, a bez wątpienia
najpopularniejszy język programowania wyższego
poziomu na Amidze.
Można by wymienić wiele zalet języka C, ale naszym
zdaniem naprawdę ważne są tylko trzy.
* Prostota. C jest bardzo surowy, można wręcz
powiedzieć -- ascetyczny. Trzon języka jest
niezwykle skromny, dzięki czemu łatwo go
opanować (naszym zdaniem znacznie łatwiej niż
np. promowany w polskim szkolnictwie Pascal).
C jest elastyczny i nakłada na programistę
niewiele ograniczeń.
* Powszechność. Niemal każda publikacja na temat
programowania Amigi dotyczy właśnie tego
języka. W tym języku jest napisany system
operacyjny naszego komputera (niecały część,
tzn. fragmenty wymagające maksymalnej
prędkości, została napisana w asemblerze).
* Bardzo dobre kompilatory. Jest ich wiele. Od
tych działających już na A500 z 0,5 MB RAM, aż
po kolubryny ledwie pracujące na A1200 z 6 MB
RAM.
Kompilatory
Co do różnych kompilatorów, to od razu pojawia się
pewien problem. Po prostu różne kompilatory są ze
sobą nie do końca zgodne. My, podobnie jak [sts]
(ach, wy tajniacy -- patrz Magazyn AMIGA 6/94,
artykuł "SAS C")[ w ramach odtajnienia - Stanisław
Szczygieł - jakby ktoś tego nie wiedział - przyp.
mp] uważamy, że najlepszy z dostępnych na Amidze
kompilatorów to SAS/C 6, więc wszystkie przykłady
będą pisane z myślą o nim. Nie powinno być jednak
większych problemów przy używaniu innych
kompilatorów.
Co do opcji kompilatora, to sugerujemy zadbać, aby
typ "char" był bezznakowy (wartości od 0 do 255, a
nie od -128 do 127). W SAS/C 6 robi się to podając
opcję UCHAR, w Manx Aztec C 5 opcję -PP, w GNU CC
opcję -FUNSIGNED-CHAR. Należy się również upewnić,
że typ "int" jest 32-bitowy (long-int), a nie
16-bitowy (short-int) -- kompilatory powinny mieć
standardowo ustawioną właściwą wartość.
Program kursu
Czas napisać, czego chcielibyśmy Was nauczyć i co
zakładamy, że już umiecie. Zaczynając od tego
drugiego: NIE będziemy uczyć podstaw języka C. Na
ten temat istnieje mnóstwo publikacji, poza tym
podstawy są na każdym komputerze takie same, a
więc można się uczyć nawet z książek o pececie. My
polecamy zdobycie "biblii języka C" -- książki
"Język ANSI C" autorstwa Briana W. Kernighana i
Dennisa M. Ritchie (jest to nowa pozycja, wydana
przez WNT, Warszawa 1994 -- nie polecamy
pierwszego, przestarzałego już, wydania). Książka
ta opisuje w dość przystępny sposób standard
języka, bez żadnych pecetowych rozszerzeń (jest
tylko jeden rozdział o Unixie).
Czego więc chcemy Was nauczyć? Chcemy dać Wam
podstawy niezbędne do pisania zgodnych ze
środowiskiem systemu operacyjnego aplikacji. Można
by więc powiedzieć, że kurs ten to nie kurs języka
C, ale kurs programowania zgodnego z systemem
operacyjnym. Co chcemy przez to powiedzieć? To, że
zdecydowaną większoć zawartych w tym kursie
informacji będą mogli wykorzystywać nie tylko
programujący w C, ale również programujący w
asemblerze, Pascalu, E itd.
Nasz kurs języka C nie jest pierwszym w polskiej
prasie komputerowej. Co więc nowego mamy zamiar
wnieść w stosunku do kursów prowadzonych kiedyś
przez Bohdana R. Raua w "Amigowcu" i Jarosława
Chrostowskiego w "C64+4 & AMIGA"? Przede wszystkim
nasz kurs będzie nowocześniejszy. System
operacyjny się zmienia (no, ostatnio nieco
wolniej, ale miejmy nadzieję, że to zastój
chwilowy). Kurs z "Amigowca" bazował na systemie
operacyjnym w wersji 1.3, my tymczasem mamy zamiar
bazować na wersji 2.04 systemu, nie zapominając
przy tym o systemach 2.1 i 3.0. Chcielibyśmy to
jeszcze raz podkreślić: BĘDZIEMY PISALI O OS
2.04+. Większość przykładów (jeśli nie wszystkie)
będzie wymagać przynajmniej tej wersji systemu
operacyjnego.
Zaczniemy od tego, co z punktu widzenia
użytkowników jest najbardziej widoczne -- od
graficznego interfejsu użytkownika, tzn. ekranów,
okien, gadżetów, menu. System operacyjny Amigi to
jednak nie tylko okienka. Istnieje masa wielce
przydatnych bibliotek nie związanych z GUI
(Graphic User Interface -- Graficzny Interfejs
Użytkownika). Opiszemy więc "exec.library", czyli
System Executor -- bibliotekę zarządzającą całym
systemem, oraz "dos.library" -- bibliotekę
zarządzającą wysokopoziomowym I/O. Właściwie to
chcielibyśmy po trochu opisać wszystkie
najważniejsze elementy systemu operacyjnego, aby
dać Wam dobre rozeznanie w tym, co system jest w
stanie programiście zaoferować. Chcielibyśmy więc
napisać również o "amigaguide.library",
"commodities.library", "workbench.library",
"locale.library", "iffparse.library" itd. Są to,
rzecz jasna, nasze pobożne życzenia, a czy te
ambitne plany uda nam się zrealizować, czas
pokaże.
Jak już wcześniej wspomnieliśmy, chcemy dać Wam
"podstawy". Oznacza to, że NIE będziemy dokładnie
opisywać wszystkich funkcji, pól struktur czy
stałych. Jest to po prostu fizycznie niemożliwe.
System operacyjny jest tak obszerny, że jego pełny
opis zajmuje tysiące stron (mówimy o serii
książkowej "Reference Manuals"). Z konieczności
więc będziemy opisywali tylko najważniejsze
elementy systemu, najczęściej używane w typowych
programach. W tekście artykułu znajdą się też z
pewnością pewne niedomówienia oraz półprawdy --
chodzi po prostu o to, że gdybymy zbyt często
zastrzegali się, że "nie do końca jest tak, jak
piszemy, bo...", to niezbyt dobrze obeznani z
tematem Czytelnicy po prostu utraciliby główny
wątek artykułu. Zagubiliby się w tych wszystkich
niuansach (albo zaczęliby sądzić, że "ktoś"
usiłuje ich "zrobić w konia"); poza tym artykuł
znacznie by się wtedy wydłużył.
Rzecz jasna, będziemy się starali robić jak
najmniej błędów. Najprawdopodobniej wszystkie
przykłady będą intensywnie testowane przy użyciu
opisanych przeze mnie w Magazynie AMIGA
12/94--1/95 debuggerów. Jeżeli jednak znajdziecie
w artykule bądˇ w przykładach jakie błędy, lub też
macie inne uwagi/sugestie dotyczące tego kursu, to
piszcie do redakcji. Nie oczekujcie jednak, że
Wasze listy znajdą odzwierciedlenie już w kolejnej
części kursu. Ze względu na długi cykl wydawniczy
Magazynu AMIGA oraz na to, że piszemy z
dwumiesięczną "zakładką", Wasze uwagi możemy
uwzględnić najwcześniej 3 miesiące po ich
otrzymaniu.
"Inkludy"
Chcielibyśmy skupić się na chwilę na "inkludach",
czyli plikach nagłówkowych, dołączanych przez
kompilator dyrektywą "#include". Na Amidze są one
bardzo rozszerzone w stosunku do standardu ANSI --
zawierają wszystkie definicje i deklaracje
niezbędne do pisania aplikacji korzystających z
zasobów systemu operacyjnego. Istnieją różne
wersje "inkludów" -- musicie mieć je przynajmniej
w wersji dla OS 2.0, a w niektórych wypadkach
potrzebne będą inkludy dla OS 3.0 (my używamy
inkludów dla OS 3.1 i Wam też to polecamy -- można
je znaleźć na dysku CD Freda Fisha bądź w
Internecie: serwer "ftp.rz.uni-wuerzburg.de",
katalog "pub/amiga/frozenfish/bbs/cbm"). O
strukturze inkludów powinnicie wiedzieć tyle, że
opisy "device'ów" znajdują się w katalogu
"devices", a bibliotek w katalogu "libraries" (nie
zawsze -- duże biblioteki mają osobne katalogi,
np. "exec", "intuition", "dos"). W katalogu "clib"
znajdują się prototypy (deklaracje) funkcji z
poszczególnych bibliotek i niektórych device'ów.
Twórcy kompilatorów tworzą często dodatkowy
katalog, o nazwie "pragmas" bądź "inline",
zawierający pewne informacje umożliwiające
kompilatorom tworzenie bardziej efektywnych
wywołań funkcji systemowych. Czasami tworzą też
oni katalog "proto", zawierający proste pliki
powodujące załadowanie jednym ruchem deklaracji i
pragm, np. wykonanie w SAS/C "#include
<proto/intuition.h>" powoduje dołączenie
"clib/intuition_protos.h" i
"pragmas/intuition_pragmas.h". Niestety, nie we
wszystkich kompilatorach tak jest. Niektóre nie
mają plików "proto" i trzeba "ręcznie" dołączać
deklaracje i pragmy. Niektóre nie mają również
pragm -- wtedy dołącza się tylko deklaracje.
Wydaje mi się, że jak na wstęp, to ten fragment
zrobił się to nieco przydługi. Aby więc nie tracić
czasu ani cennego miejsca, już dziś zaczynamy
"regularny" kurs. "Oddaję więc klawiaturę" w ręce
mego wspólnika:
"Ależ dziękuję Kamilku za okazaną mi łaskę w
postaci wysłużonej dyskietki, o otrzymaniu
klawiatury nie śmiałbym marzyć nawet skrycie."
Co to jest multitasking?
Multitasking jest to możliwość wykonywania
jednocześnie (lub jeśli ktoś chciałby być
dokładny, prawie jednocześnie) kilku programów.
Taki system pracy komputera pozwala na pełniejsze
wykorzystanie mocy procesora z dwóch zasadniczych
powodów.
Po pierwsze komputer domowy w większości wypadków
czeka na sygnał od użytkownika lub urządzeń
zewnętrznych, w tym czasie inny program może
efektywnie wykorzystywać system (obciążenie
procesora da się łatwo sprawdzić, np. za pomocą
programu Spy -- w graficzny sposób przedstawia on
stopień zajęcia procesora, czas, przez jaki
pracował on "na pełnych obrotach" oraz przez jaki
"zbijał bąki").
Drugim równie ważnym powodem budowy takich
systemów jest możliwość współpracy kilku
niezależnych programów, które mogą na bieżąco
przekazywać sobie wyniki swojej pracy, a w ten
sposób zaoszczędzić czas potrzebny na zapis i
odczyt danych z/do plików, co jest znaczące przy
większych obliczeniach. Taki sposób pracy jest
szalenie wygodny. W tym właśnie celu programy są
wyposażane w interface ARexxa (Amiga Rexx jest
językiem stworzonym w celu nadzorowania pracy
programów (patrz Magazyn AMIGA 1/92, wrzesień).
Niestety, nie ma róży bez kolców: z
multitaskingiem trzeba uważać. Pisząc programy
należy pamiętać o tym, że podczas ich wykonywania
mogą się znajdować w pamięci inne programy. To
jest nasz problem -- problem programistów, którzy
muszą pamiętać, że "nie są sami", że nie wolno
"grzebać" w nie swojej pamięci oraz zmuszać
procesora do pracy, jeli nie jest to konieczne.
Zabronione jest tworzenie tak zwanych busy loopów,
czyli wykonujących się bez przerwy pętli służących
do oczekiwania na informację (dokładniej zajmiemy
się tym przy okazji opisu funkcji Execa Wait()
oraz portów).
Kolejnym problemem jest nieco wolniejsze
funkcjonowanie programów, jednak twórcy borykają
się z nim raczej rzadko, ponieważ zastosowanie
multitaskingu spowalnia działanie programów w
niewielkim stopniu (poza tym można programowi
nadać wyższy priorytet, dzięki czemu będzie on
miał pierwszeństwo w stosunku do pozostałych).
Jednak jeśli komuś zależałoby na wyciśnięciu z
maszyny wszystkiego, może wyłączyć pozostałe
zadania (taski). Do tego celu przeznaczone są
funkcje Execa (Forbid(), Permit()), jednak nie
będziemy się nimi zajmowali (przynajmniej na
początku).
Amigowski multitasking jest dość zgrabnie
zorganizowany -- nawet jeli dojdzie do katastrofy
i jakiś program się "powiesi", to nie oznacza to
całkowitej klęski systemu. Problem taki jest znany
użytkownikom programu Windows 3.1, który ma
multitasking kooperatywny, czyli, mówiąc
złośliwie, multitasking bez multitaskingu --
programy zwalniają procesor, gdy tego pragną, brak
tam nadzorcy (patrz PC World Komputer Luty 1994:
"Dos umarł!"). System zastosowany w Amidze wygląda
inaczej, powiedziałbym, że jest pod wieloma
względami lepszy. Na tym komputerze stale
funkcjonuje program w trybie nadzorcy (Supervisor
-- tryb procesora), zajmujący się przydzielaniem
czasu procesora programom znajdującym się na
liście oczekujących (ten program to taki komitet
kolejkowy). Istnienie "wszechmocnego" nadzorcy
pozwala na wykrycie i usunięcie z listy
zawieszonego programu, dzięki czemu system może
działać nadal. W zasadzie mowa tu o anormalnym
zachowaniu się oprogramowania, jednak takie
nieszczęścia z całą pewnością spotkają Czytelników
podczas pisania i testowania programów. Tyle
wiedzy na temat multitaskingu powinno wystarczyć.
Nareszcie możemy się zająć bibliotekami, które są
wypełnione po same brzegi funkcjami.
Dlaczego biblioteki?
Stosowanie bibliotek zewnętrznych wynika z
zastosowania multitaskingu. W wypadku pecetowego
DOS-u funkcje znajdują się w bibliotekach, które
przy linkowaniu (konsolidacji) programu są doń
dołączane -- postępowanie takie znacznie wydłuża
kod programu, a poza tym w wypadku funkcjonowania
kilku programów jednocześnie jest wprost zabójcze
dla pamięci (w wypadku Amigi również istnieją
takie biblioteki, ale często ich praca ogranicza
się do otwarcia zewnętrznej biblioteki i wywołania
jakiejś jej funkcji). Biblioteki amigowe to wygoda
i oszczędność -- spróbujmy to udowodnić. W chwili,
gdy kilka programów korzysta z takiej samej
funkcji, powiedzmy z file-requestera (okna wyboru
plików), nie muszą one mieć takiej funkcji w swoim
kodzie -- wystarczy, że skorzystają z biblioteki
systemowej "asl.library", która zawiera taką
funkcję. Wykorzystywanie funkcji bibliotecznych
oszczędza również czas przeznaczony na pisanie i
testowanie programów, poza tym dzięki zastosowaniu
bibliotek programy są podobne do siebie zarówno
pod względem obsługi, jak i graficznego interfejsu
użytkownika (GUI). Spróbujmy sobie wyobrazić, że
każde okno ma gadżet zamykania w innym miejscu, a
zrozumiemy, co znaczy standard.
Częć bibliotek znajduje się w pamięci stałej
komputera (od systemu 2.0 jest tego 512 KB),
pozostałe biblioteki znajdują się na dysku,
przeważnie w katalogu "LIBS:". Podstawową
biblioteką jest "exec.library", bez jej udziału
nie jest możliwe funkcjonowanie właściwie żadnego
programu. Biblioteka ta jest otwierana przy
inicjacji systemu i pozostaje otwarta do końca
jego pracy. W jej zasobach znajdują się funkcje
służące do przydzielania pamięci, otwierania
innych bibliotek oraz wiele innych niezwykle
pożytecznych narzędzi. Ze względu na swój
specyficzny charakter biblioteka Exec musi być
dostępna dla każdego i w każdej chwili. Z tej
właśnie przyczyny, w przeciwieństwie do
pozostałych bibliotek, nie ma potrzeby jej
otwierania.
Dlaczego otwieramy biblioteki?
Biblioteka podczas otwierania jest umieszczana w
pamięci, jeśli pochodzi z dysku, jeśli natomiast
pochodzi z ROM-u, to pozostaje w nim, a w pamięci
RAM zapisywana jest jedynie pomocnicza struktura
opisująca bibliotekę. W celu otwarcia biblioteki
należy posłużyć się funkcją Execa:
struct Library *OpenLibrary( UBYTE *libName,
unsigned long version );
Pierwszym argumentem funkcji jest wskaźnik na
nazwę biblioteki, drugim minimalna wymagana wersja
biblioteki. Jeżeli funkcja odnajdzie żądaną
bibliotekę w odpowiedniej wersji, to zwróci adres
opisującej ją struktury "Library", w przeciwnym
wypadku zwróci 0 (NULL). Dlaczego istnieje
parametr ograniczający wersję biblioteki?
Odpowiedź jest prosta: biblioteki rozrastają się i
mają coraz więcej funkcji, programiści są i często
muszą być wybredni, właśnie dlatego umiera system
w wersji 1.3, a poprzednie już zostały pochowane.
Funkcja OpenLibrary w tej formie pojawiła się w
systemie 1.2 -- wcześniej istniała funkcja o tej
samej nazwie różniąca się tym, że nie sprawdzała,
jaką wersję biblioteki otwiera. Stara funkcja
została zachowana dla utrzymania zgodności systemu
z już napisanym oprogramowaniem -- obecnie nazywa
się OldOpenLibrary(), ma jedynie pierwszy
argument.
Wartość zwróconą przez OpenLibrary() należy
zapamiętać w zmiennej wskaźnikowej o ściśle
określonej nazwie (np. IntuitionBase dla
"intuition.library", ReqToolsBase dla
"reqtools.library" itd.), ponieważ zmienna ta jest
wykorzystywana przy wywołaniach funkcji z danej
biblioteki.
Dlaczego zamykamy biblioteki?
Kiedy otwarta przez nas biblioteka nie jest nam
już dłużej potrzebna, czyli zwykle pod koniec
programu, należy ją zamknąć. Służy do tego funkcja
Execa:
void CloseLibrary( struct Library *library );
Jako jej parametr należy podać wartość zwróconą
przez OpenLibrary(), czyli wskaźnik na strukturę
"Library".
Początkujący programiści często mają wątpliwości,
czy zamykanie bibliotek rzeczywiście jest
potrzebne. Faktem jest, że jeżeli się biblioteki
nie zamknie, to nie będzie jakiejś strasznej
katastrofy (nie dojdzie do zawieszenia programu),
jednak porządny program ZAWSZE powinien zostawiać
po sobie porządek -- jeżeli się coś (bibliotekę,
czcionkę, plik, okno itd.) otworzyło (bądź
utworzyło), to należy to coś zamknąć, aby zapewnić
sprawne działanie systemu, umożliwić zwrot
przydzielonej przy otwieraniu pamięci.
Proponujemy przećwiczyć otwieranie i zamykanie
bibliotek na przykładzie
Listing nr 1
W tym programiku otwieramy biblioteki, ich adresy
przechowujemy w zmiennych o odpowiednich nazwach,
po czym zamykamy biblioteki. Nie korzystamy
jeszcze z żadnych funkcji zawartych w bibliotekach
(nie wszystko naraz).
Tym, którzy dopiero zaczynają programować w C,
należy się drobne wyjaśnienie odnośnie jednej z
linii programu:
if (GfxBase=(struct
GfxBase*)OpenLibrary("graphics.library", 0))
Język C jest dosyć elastyczny i pozwala na
jednoczesne przypisanie wartości i sprawdzenie, czy
wartość ta jest różna od zera. Z takimi językowymi
idiomami będziemy się często spotykać; dla
"ułatwienia" będą jeszcze wzbogacone o znak "!",
czyli po prostu negację (UWAGA: niektóre
kompilatory mogą wygenerować ostrzeżenie o
możliwości wystąpienia błędu w wypadku takiej
formy, jest ona jednak w pełni poprawna, a
ostrzeżenia są po to, by odszukać literówki -- '='
zamiast '=='). Podobnie należałoby przypomnieć, co
oznacza dziwoląg umieszczony poniżej, ale tym
zajmiemy się za chwilę.
IntuitionBase=(struct
IntuitionBase*)OpenLibrary...
Struktura opisująca bibliotekę
Jak już mówiliśmy, podczas otwierania biblioteki
otrzymujemy wskaźnik na strukturę "Library".
Znajomość tej struktury nie jest może niezbędna
przy pisaniu programów, zawsze jednak warto
wiedzieć dokładnie, "co w trawie piszczy". Jednym
z jej pól jest "lib_OpenCnt", w tym polu jest
zapisana liczba użytkowników korzystających z
biblioteki w danej chwili. Jeli biblioteka straci
wszystkich użytkowników (pole lib_OpenCnt == 0),
to może zostać usunięta z pamięci i tak się
stanie, ale dopiero w wypadku braków pamięci. Jeli
problemy braku pamięci nie wystąpią, biblioteka
będzie pozostawać w pamięci, pomimo iż nikt z niej
nie korzysta (czeka na lepsze czasy). PrzejdŹmy do
pól "lib_Version" i "lib_Revision". W polach tych
zapisany jest numer wersji biblioteki. Właśnie po
tych polach można sprawdzić, z jaką wersją systemu
pracujemy.
Listing nr 2
W kolejnych programach będziemy korzystać z
funkcji check_os(), której dla oszczędności
miejsca nie będziemy za każdym razem przepisywać,
więc Czytelnik będzie zmuszony dołączać ją (oraz
definicje stałych "OS_xx") do kolejnych programów.
Dwa słowa odnośnie UWORD w deklaracji funkcji
check_os() -- jest to nic innego jak "unsigned
short int". Analogicznie ULONG oznacza "unsigned
long int", a UBYTE -- "unsigned char".
W tym krótkim programiku pokazaliśmy, jak można
wykorzystać zmienną "SysBase", która jest
wskaźnikiem na strukturę "ExecBase" (wartoć tej
zmiennej nadaje dołączony podczas linkowania
programu moduł startowy, pobierając ją z komórki
pamięci pod adresem 0x00000004 -- ta informacja
jest ważna raczej dla programujących w
asemblerze). Zawartoć struktury "ExecBase" można
poznać analizując systemowe inkludy, a ściślej
mówiąc, plik "exec/execbase.h". Pierwszym polem
tej struktury jest "LibNode" typu "Library", co
oznacza, że struktura ta jest rozbudowaną wersją
struktury "Library", dzięki czemu możemy swobodnie
zastosować rzutowanie typów (ang. casting), z
którym spotkalimy się już w pierwszym przykładzie
podczas przypisywania wartości zmiennym
"IntuitionBase" i "GfxBase". Poza polem "LibNode"
struktura "ExecBase" zawiera pewne specyficzne dla
niej dane, takie jak np. adres obecnie
wykonywanego zadania ("ThisTask"), typ procesora
("AttnFlags") oraz wiele innych, często
prywatnych, informacji (tj. takich, których
aplikacje nie powinny wykorzystywać).
Do danych zawartych w polu LibNode struktury
"ExecBase" możemy odwoływać się na dwa różne
sposoby:
ver=SysBase->LibNode.lib_Version;
ver=((struct Library*)SysBase)->lib_Version;
Analogicznie możemy postępować z pozostałymi
rozbudowanymi strukturami, opisującymi biblioteki:
opc=IntuitionBase->LibNode.lib_OpenCnt;
opc=((struct Library*)IntuitionBase)->lib_OpenCnt;
Odwołania te wskazują dokładnie na tę samą
zmienną, obie formy są poprawne.
Powróćmy do struktury "ExecBase". Zapoznajmy się z
kolejnym przykładem.
Listing nr 3
Pole "ThisTask", jak już wspomnieliśmy, zawiera
adres struktury "Task", w której znajdują się
informacje o wykonywanym w danej chwili zadaniu, a
więc naszym programie. Informacji tych jest cała
masa (patrz "exec/tasks.h"), my pobieramy tylko te
o nazwie procesu (będzie to najprawdopodobniej
"Shell Process", choć mogą się zdarzyć i inne)
oraz jego priorytecie (niemal zawsze 0).
Komentarza może wymagać użyty w nim "for". Stałe
symboliczne "AFB_68xxx" są zdefiniowane w pliku
"exec/execbase.h". Stała AFB_68010 ma wartość 0,
AFB_68020 to 1 itd. Dane zawarte w polu
"AttnFlags" są zapisane w formie maski bitowej;
gdy jest obecny dany procesor, to bit o numerze
"AFB_68xxx" jest ustawiony, ustawione są jednak
również niższe bity, tyczące się starszych
procesorów -- z tego powodu zastosowaliśmy pętlę z
licznikiem malejącym, zaczynającą sprawdzanie od
najnowszych procesorów. Warunek "licznik==-1"
będzie spełniony dla procesora MC68000, który nie
jest odnotowany w "AttnFlags". Przykład ten
wyświetli nieprawdziwe informacje dla procesora
MC68060 (potraktuje go najprawdopodobniej jako
MC68040), jako że odpowiedniej stałej brakuje w
pliku "exec/execbase.h". Warto w tym momencie
wspomnieć o różnicy pomiędzy stałymi "AFB_68xxx" i
"AFF_68xxx": stałe "B" oznaczają NUMER bitu, a
stałe "F" są gotową MASKĄ bitową (można by
nieformalnie napisać: "F"=1<<"B"). Radzimy o tym
pamiętać, ponieważ jest to ogólnie używana
konwencja, a pomylenie jednych stałych z drugimi
staje się przyczyną błędnego działania programu.
Na tym kończymy dzisiejszy odcinek i zapraszamy do
lektury następnej części kursu. A za miesiąc
bierzemy się do okien, zajmiemy się również
informacjami pochodzącymi od okna.