1087 - Interpreter, Kompilator, Assembler Cz.3

Zanim przejdziemy do assemblera, który będzie dziś naszym głównym tematem, jeszcze kilka słów o wyborze translatora. Porównując różne programy tego typu należy brać pod uwagę szybkość działania, ilość zajmowanej pamięci, ale także łatwość korzystania.

Dalej, ważne jest, czy translator daje dobre, szczegółowe diagnostyki błędów, czy też pisze tylko "syntax error" i użytkownik musi głowić się sam na czym polega błąd. Warto też wziąć pod uwagę dodatkowe udogodnienia. takie jak np. możliwość śledzenia programu w trakcie wykonywania, dołączanie procedur pisanych w innych językach, dostęp do bibliotek podprogramów użytkowych, możliwość tworzenia własnych bibliotek. Decydującym czynnikiem będzie to, czy translator nie wymaga urządzeń, którymi nie dysponujemy, np. stacji dysków, i z drugiej strony, czy pozwoli wykorzystywać wszystkie urządzenia, które posiadamy, np. drążek sterowy lub mysz.

Niezależnie od wszystkich różnic i szczegółów technicznych języki wysokiego poziomu i ich translatory dość dobrze wykonują swoje zadanie, którym jest pośredniczenie między poziomem, na którym wygodnie jest pracować człowiekowi, a poziomem zer i jedynek języka wewnętrznego maszyny. Jednak nic za darmo. Ceną, którą trzeba zapłacić za taki model pracy jest zasłonięcie przed programistą części możliwości komputera. Przykłady: jeśli komputer ma gniazdo do podłączenia drążka sterowego, to na pewno procesor ma możliwość komunikowania się z tym gniazdem. Natomiast program np. w BASIC-u będzie miał tę możliwość tylko pod warunkiem, że w repertuarze instrukcji rozpoznawanych przez translator znalazły się odpowiednie instrukcje wejścia/wyjścia. Rozmieszczanie programu i zmiennych w pamięci operacyjnej odbywa się automatycznie, przez co tracimy bezpośredni dostęp do tej pamięci*). Procesor może operować na pojedynczych bitach, zaś w wielu językach programowania możemy takie operacje wykonywać tylko za pomocą specjalnych sztuczek, lub nawet wcale nie możemy, itd.

Wróćmy teraz na chwilę do historii. Przejście do pisania programu jako ciągu zer i jedynek do języków wysokiego poziomu nie nastąpiło jednym skokiem. Etapów pośrednich było kilka, a jednym z nich był własnie assembler - narzędzie z jednej strony na tyle wygodne, że może być zaakceptowane przez człowieka, z drugiej strony dające pełen dostęp do wszystkich możliwości wbudowanych w maszynę przez jej konstruktorów. Dzieje się to dlatego, że assembler jest dużo bliżej języka wewnętrznego niż języki programowania wysokiego poziomu. W zasadzie jeden rozkaz assemblera jest przekładany na jeden rozkaz procesora.

Skoro użycie assemblera "odsłania" nam procesor, to warto najpierw przyjrzeć się temu urządzeniu nieco dokładniej. Oczywiście wiele szczegółów technicznych musimy pominąć, aby otrzymać model odpowiednio ogólny. Wewnątrz procesora znajdziemy zwykle tzw. arytmometr, czyli jednostkę wykonującą wszystkie operacje arytmetyczne i logiczne na ciągach bitów. Żeby arytmometr miał skąd pobierać dane i gdzie umieszczać wyniki operacji, procesor zawiera rejestry, czyli urządzenia pozwalające zapisywać ciągi bitów - zwykle takiej samej długości jak długość komórki pamięci. Dwie grupy rozkazów procesora już można przewidzieć: dodanie, odjęcie itd. zawartości dwóch rejestrów i umieszczenie wyniku w rejestrze, oraz druga grupa, wszelkiego rodzaju przesłania danych między komórkami pamięci a rejestrami, np. wpisz zawartość komórki pamięci o podanym adresie do rejestru A. Odwołajmy się tu do języków programowania, prosta instrukcja A=B+C będzie zrealizowana jako następujący ciąg instrukcji procesora: załadowanie wartości B i C z odpowiednich komórek do rejestrów procesora, dodanie wartości rejestrów i zapamiętanie, także w rejestrze, wyniku, przepisanie wyniku z rejestru do odpowiedniej komórki PaO**). Wydaje się to być dosyć pracochłonne, ale idzie sprawnie, bo procesor wykonuje przynajmniej kilkaset tysięcy elementarnych operacji w ciągu sekundy.

Wróćmy do rozkazów. Wspomniałem, że są one zapisane w kolejnych komórkach pamięci, i - w takiej samej kolejności jak były zapisane - pobierane do wykonania przez procesor. Jednak w programach mogą występować instrukcje warunkowe i wtedy część kodu może być pomijana. Zrealizujemy to dzięki następnej grupie - instrukcjom skoku. Ich wykonanie to nic innego jak "samoprzestawienie się" procesora na pobieranie do wykonania z innego miejsca PaO. Oczywiście po takim przestawieniu dalej obowiązuje pobieranie rozkazów po kolei - aż do napotkania następnej instrukcji skoku.

Dodajmy jeszcze instrukcje przesyłania zawartości rejestrów z (do urządzeń wejścia) wyjścia, i będziemy mieli bardzo ogólny i znacznie uproszczony obraz procesora***) - a więc równocześnie obraz tego, co można zrobić w assemblerze. Postawmy się teraz w roli pionierów informatyki i spróbujmy pomyśleć, jak nie tracąc żadnych możliwości maszyny, ułatwić sobie życie.

Na pewno warto nadać wszystkim rozkazom procesora krótkie, łatwe do zapamiętania nazwy, których będziemy używać zamiast zero-jedynkowych kodów. To jednak nie wszystko. Większość rozkazów wymaga podania jako dodatkowej informacji adresu fizycznego komórki pamięci, której rozkaz ma dotyczyć. Bardzo przyda się możliwość nadawania komórkom nazw i używania później tych nazw w miejscach, gdzie musi wystąpić adres. (Własnie z tej możliwości zastępowania adresów symbolami wzięła się inna nazwa assemblera - "język adresów symbolicznych".) Także rejestry, które w procesorze mają binarne identyfikatory, otrzymują nazwy. Ważnym udogodnieniem jest możliwość podawania w programach liczb w systemach innych niż dwójkowy - najczęściej dziesiętnie, ósemkowo, szesnastkowo.

Dla opisanego języka potrzebny jest translator, który przełoży napisane w nim programy na język wewnętrzny maszyny. Jego działanie jest stosunkowo proste. Nazwy rozkazów trzeba zastąpić odpowiednimi kodami, zamiast nazw komórek pamięci wpisać odpowiednie adresy fizyczne, wszystkie liczby przeliczyć na wartości dwójkowe. Taki translator nazywany jest zwykle assemblerem - to nie pomyłka, to samo słowo oznacza dwie różne rzeczy: język i translator tego języka.

Nie jest również pomyłką wyliczenie wśród translatora tylko "mechanicznego" przekodowania zapisu instrukcji na binarne kody, gdyż wszystkie pozostałe problemy programista musi rozwiązać sam. Mam tu na myśli np. rozmieszczenie w pamięci danych i programu, czy dbałość o sens wykonywanych operacji BASIC nie pozwoli dodać zmiennej liczbowej do zmiennej tekstowej, w assemblerze nie ma zmiennych, są tylko komórki pamięci, a w nich bity. Sprzęt daje możliwość dodania do siebie zawartości dowolnych dwóch komórek pamięci, assembler ma nam udostępnić wszystkie możliwości sprzętu, więc do naszej decyzji zostaje, czy będziemy dodawać do siebie komórki, w których poprzednio zapisaliśmy teksty lub rozkazy dla procesora (zdarzają się sytuacje, w których wykonanie takich operacji jest celowe).

Programowanie w assemblerze jest trudniejsze niż w językach wysokiego poziomu, wymaga głębszej znajomości działania sprzętu, jednak w wielu zastosowaniach bywa potrzebne, lub nawet niezbędne. Dodatkową zaletą assemblera jest możliwość stworzenia dla fragmentów programu dużo efektywniejszego kodu wynikowego niż wygenerowany przez translator języka wysokiego poziomu. Często tryb postępowania jest następujący: do programu pisanego w języku wysokiego poziomu dołącza się podprogramy w assemblerze. Jest to nic innego jak wspomniany poprzednio dobór języka programowania do rozwiązywanego problemu - tutaj w ramach tego samego zadania.

Powszechnie uważa się również, że umiejętność programowania w assemblerze to jakby wyższy stopień wtajemniczenia. Programiści znający chociaż jeden assembler lepiej rozumieją sposób, w jaki komputer wykonuje program napisany w języku wysokiego poziomu, a to pozwala im pisać lepsze programy.

*) Większość interpreterów BASIC-a na mikrokomputery pozwala odwoływać się bezpośrednio do PAO instrukcjami PEEK i POKE. Bezpieczne są jednak tylko odwołania do obszarów, których zawartość jest dokładnie opisana w instrukcji, np. pamięć obrazu.

**) Oczywiście wiele zależy od konkretnego procesora. Np. można spotkać rozkaz pozwalający dodać zapisaną w rejestrze wartość bezpośrednio do zawartości komórki do rejestru.

***) Pominąłem m.in. istnienie grupy szczególnych wąsko specjalizowanych rejestrów.

Andrzej Pilaszek - Bajtek 08,09,10 - 1987r.