Większości czytelników znane są wymienione w tytule pojęcia, często jest to jednak znajomość dość pobieżna, więc spróbujmy przyjrzeć się im dokładniej.
Żeby wszystko było jasne musimy przypomnieć sobie jak z punktu widzenia programisty działa komputer. Jak wiemy, zarówno program jak i dane przechowywane są w pamięci operacyjnej (PaO). Pamięć ta składa się z określonej liczby komórek (np. ośmiobitowych), których zawartość może być zapisywana lub odczytywana przez procesor. Do identyfikacji komórek (wskazania, której z nich ma dotyczyć zapis lub odczyt) służą tzw. adresy fizyczne komórek. Procesor natomiast ma ustalony pewien repertuar rozkazów, które "umie" wykonać. Potrafi on również pobrać zapisany w komórce pamięci ciąg bitów (zer i jedynek), określić który z jego rozkazów ten ciąg oznacza, a następnie ten zakodowany rozkaz wykonać. Tak więc wykonanie wpisanego do PaO programu wygląda następująco: rozkazy zapisane w kolejnych komórkach pamięci, są przez procesor pobierane, dekodowane i wykonywane. Nie mówiliśmy tego wprost, ale to chyba oczywiste, że cały proces jest WYŁĄCZNIE zero-jedynkowy. Jedyne obiekty, na których umie operować procesor, to adresy fizyczne i zawartość komórek. Przy czym, w zależności od sytuacji, zawartość ta traktowana jest raz jako rozkazy, raz jako dane dla rozkazów lub ich wyniki. Dlatego żeby procesor mógł dodać dwie liczby, to po pierwsze muszą być one umieszczone w PaO, a po drugie, we wpisanym w PaO programie musi się znaleźć rozkaz dodania, zawierający informację o tym , gdzie (w których komórkach) znajdują się liczby, które mają być dodane. Ta informacja może być przekazana tylko jako (zero-jedynkowe) fizyczne adresy komórek. Jednym słowem język wewnętrzny maszyny, to ciągi zer i jedynek, w których zakodowana jest informacja o rozkazach do wykonania przez procesor oraz o lokacji argumentów i wyników rozkazów. Wewnątrz maszyny język ten jest reprezentowany przez impulsy elektryczne, człowiek mógłby go używać pisząc zera i jedynki na papierze, a następnie wprowadzając je do komputera, np. za pośrednictwem papierowej taśmy perforowanej, na której dziurka oznacza jedynkę, a brak dziurki - zero (lub na odwrót). Przygotowywanie programów w ten sposób jest, delikatnie mówiąc, nieco niewygodne. Niemniej jednak, tak właśnie były programowane pierwsze komputery.
Żeby zbliżyć się do naszego zasadniczego celu przeskoczmy od razu w drugą skrajność i zobaczmy jak się to robi teraz. Stworzono języki programowania* np. BASIC, ALGOL, PASCAL, FORTRAN itd. W językach tych można używać obiektów znacznie przyjemniejszych do operowania niż komórka pamięci, takich jak zmienne, liczby, teksty czy tablice. Stosunkowo wygodnie (oczywiście z punktu widzenia człowieka) można zapisywać jakie operacje mają być wykonane przez komputer. Równocześnie, dla każdego z tych języków, istnieją reguły określające jak poszczególne instrukcje powinny być zrealizowane przez komputer. Na podstawie tych reguł można dokonać przekładu programu napisanego w przystępnym dla człowieka języku programowania na język wewnętrzny maszyny. Jest to ogromna praca i do tego bardzo żmudna, więc najlepiej niech wykona ją komputer. Ale żeby komputer mógł wykonać cokolwiek musi mieć program. W tym przypadku program wczytujący tekst programu i tłumaczący go na język wewnętrzny. Żeby się to wszystko nie poplątało warto przyswoić sobie kilka terminów: programy tłumaczące nazywamy translatorami, program do przetłumaczenia nazywamy programem lub kodem źródłowym, zaś otrzymany w wyniku tłumaczenia wynikowym. Ze względu na zastosowaną metodę rozwiązania zadania programy tłumaczące (translatory) możemy podzielić na kompilatory i interpretery (ang. compiler, interpreter). Zanim omówimy obie grupy zwróćmy uwagę na bardzo ważną, wspólną dla obu typów prawidłowość.
Załóżmy, że w języku wewnętrznym kod 001 oznacza dodawanie, a 011 odejmowanie dwóch liczb. Widać od razu że, na pozór błaha, zmiana jednej cyfry powoduje ogromną zmianę w wykonywaniu programu. Określa to od razu wielką precyzję z jaką musi być dokonany przekład. Żeby mogła ona być osiągnięta translator musi zawierać dokładne "rozpisanie" wszystkich możliwych instrukcji języka wyższego rzędu na instrukcje języka maszynowego. Proces tłumaczenia polega (w ogromnym uproszczeniu) na rozpoznawaniu kolejnych instrukcji programu źródłowego (ang. source) i generowaniu odpowiednich sekwencji kodu maszynowego. Na tym kończy się "inteligencja" translatora. Jeśli pisząc instrukcje programu źródłowego zrobimy najmniejszy błąd (np. pisząc przecinek zamiast kropki, czy FOR I=1 TU zamiast FOR I=1 TO) to translator stwierdza, że nie zna takiej instrukcji, więc nie wie jaki kod miałby wygenerować i sygnalizuje błąd w programie. Stąd konieczność pisania programów źródłowych bardzo dokładnie, zgodnie z opisem języka w którym programujemy.
Przyjrzyjmy się teraz działaniu interpretera. Dokonuje on przekładu kolejnych instrukcji programu źródłowego na bieżąco, tzn. wczytuje jedną instrukcję, rozpoznaje i interpretuje, czyli generuje odpowiadający jej kod maszynowy, a następnie otrzymany kod przekazuje procesorowi do wykonania. po zakończeniu wykonania pobierana jest następna instrukcja źródłowa, przekładana na kod maszynowy, wykonywana itd., aż do końca programu. Tak więc programy dla interpretera są zawsze przechowywane w postaci źródłowej**, a wersja maszynowa każdej instrukcji znika natychmiast po jej wykonaniu. Najczęściej spotykane są w tej chwili interpretery języka BASIC. Są one z reguły wyposażone w edytor ekranowy, pozwalający tworzyć i poprawiać tekst programu źródłowego, a w dowolnym momencie uruchomić wykonanie (interpretację) programu komendą RUN.
W odróżnieniu od interpretera, kompilator wczytuje cały tekst programu źródłowego od razu i generuje kod wynikowy dla wszystkich instrukcji. Otrzymujemy w ten sposób kod wynikowy całego programu, który na ogół wcale nie jest (a przynajmniej nie musi być) od razu automatycznie wykonywany, tylko zapisywany na dysku czy taśmie (kasecie), skąd może być wielokrotnie wczytywany do pamięci operacyjnej i uruchamiany w przyszłości.
Spróbujmy porównać teraz omówione metody translacji. Musimy tu wziąć pod uwagę, że na typowych mikrokomputerach domowych interpreter BASIC-a jest zapisany w ROM (pamięci stałej) i jest gotów do pracy natychmiast po włączeniu maszyny do sieci. Uruchamiając program BASIC-owy komendą RUN nie musimy nawet wiedzieć o istnieniu języka wewnętrznego maszyny i interpretera - nasz komputer wygląda tak, jakby po prostu wykonywał instrukcje BASIC-a. Jest to ogromne uproszczenie, pozwalające korzystać z komputera osobom, które prawie nic o nim nie wiedzą. Natomiast kompilator, tak jak każdy program użytkowy, trzeba najpierw załadować do pamięci operacyjnej i wskazać na miejsce, w którym znajduje się program źródłowy (najczęściej na dyskietce lub taśmie), a po skompilowaniu trzeba jeszcze uruchomić otrzymany kod wynikowy. Do tych operacji niezbędna jest pamięć zewnętrzna. Na tym jednak kończą się zasadnicze przewagi interpretacji nad kompilacją.
Przyjrzyjmy się eksploatacji gotowego programu, w którym nie wprowadzamy żadnych poprawek. W przypadku interpretera przy każdym wykonaniu treść programu musi być od nowa tłumaczona na język wewnętrzny - to jednak właśnie główny powód, dla którego programy BASIC-owe na domowych mikrokomputerach działają tak wolno. Używając kompilatora uzyskujemy kod, który może być wykonywany bez żadnych pochłaniających czas zabiegów, a więc znacznie szybciej. To samo porównanie możemy przeprowadzić nawet dla jednokrotnego wykonania programu. Jeśli w jego treści znajdzie się pętla, to znaczy te same instrukcje (wnętrze pętli) mają być wykonywane wielokrotnie, to niestety będą one również wielokrotnie interpretowane***, chyba że ... użyjemy kompilatora.
*) Ponieważ jednej instrukcji języka programowania odpowiada zwykle wiele instrukcji maszynowych, języki te często nazywane są językami wysokiego poziomu, lub wysokiego rzędu.
**) Zwykle jest to postać wstępnie przetworzona przed przechowaniem, aby zajmowała mniej miejsca i była wygodniejsza do interpretacji.
***) Można sobie wyobrazić nieco "inteligentniejszy interpreter", zawierający w sobie elementy kompilatora, który jest w stanie wykryć występowanie w programie pętli i po pierwszej translacji jej treści zapamiętać otrzymany kod.
Andrzej Pilaszek.
Bajtek 08/1987r.