W niniejszym rozdziale rozważamy podstawowe pojęcia związane ze skłonieniem systemu automatycznego do wykonania poleceń zapisanych w postaci programu źródłowego.
Formalne języki programowania, takie jak linearny Fortran, funkcjonalny LISP, proceduralny Pascal i C czy obiektowa Java albo Python, stanowią — podobnie jak sieci działań — standardowy środek dokumentowania algorytmów. Spotkaliśmy się z nimi w podrozdziale 1.3.3. właśnie przy okazji omawiania sposobów wyrażania algorytmów. Zastosowanie języków programowania jest jednak znacznie szersze: z pliku źródłowego zawierającego zapis algorytmu można uzyskać program realizujący ten algorytm, dający się uruchomić pod nadzorem systemu operacyjnego lub w specyficznym środowisku uruchomieniowym.
Ciągi poleceń zapisane w postaci programu źródłowego nadają się do wykonania przez automat. Niektóre języki programowania wymagają specyficznego środowiska, dostarczanego przez odpowiedni system użytkowy. Liczne współczesne pakiety użytkowe są wyposażona w obsługę jakiegoś języka programowania i posiadają narzędzia pomocne podczas tworzenia i uruchamiania napisanych w nim programów.
Istnieją dwa zasadniczo odmienne sposoby uruchamiania programów napisanych przy użyciu języków programowania.
Pierwszy sposób polega na przetłumaczeniu całego tekstu programu z symbolicznego języka programowania na kod wewnętrzny związany z konkretnym środowiskiem. Proces ten nosi nazwę kompilacji, zaś programy dokonujące takiego tłumaczenia określa się mianem kompilatorów.
plik z kompletnym kodem źródłowym (np. *.c) | | kompilacja ↓ plik z kompletnym kodem binarnym (np. *.o) ––→ plik z kodem symbolicznym (opcjonalnie; np. *.asm) | | konsolidacja ↓ binarny plik wykonywalny właściwy dla systemu operacyjnego (np. EXE lub ELF) | | uruchamianie programu (wiele razy, w określonym systemie operacyjnym) ↓
Wynikiem kompilacji jest program binarny zapisany w pliku wykonywalnym. Program taki da się uruchomić jako polecenie systemu operacyjnego lub środowiska wykonawczego. Użytkownik nie będący współtwórcą programu ma zazwyczaj do czynienia tylko z plikiem wykonywalnym.
Klasyczne kompilatory generują kod dla danego typu procesora i zapisują go w formacie zależnym od systemu operacyjnego. Użycie tego samego programu w systemie o innej architekturze wymaga osobnej kompilacji w celu wygenerowania odpowiedniego pliku wykonywalnego.
Drugi sposób uruchamiana programów napisanych przy użyciu języka programowania nosi nazwę interpretacji. Polega on na odłożeniu tłumaczenia tekstu poleceń do chwili bezpośrednio poprzedzającej wykonanie. W środowiskach interpretujących program jest wykonywany instrukcja po instrukcji według schematu:
| | uruchomienie kodu źródłowego | ↓←---------------------. | | przeczytaj ↑ ↓ przejdź do następnej instrukcji przetłumacz ↑ ↓ | wykonaj | ↳ ---------------------
Możliwa jest także praca interaktywna, podczas której instrukcje wprowadzane przez operatora są na bieżąco tłumaczone i wykonywane.
David Harel w klasycznej książce Rzecz o istocie informatyki (wyd. IV: WNT 2008) wskazuje kilka istotnych argumentów za budowaniem i używaniem interpreterów: skonstruowanie interpretera jest na ogół łatwiejsze, niż kompilatora; w sytuacji, kiedy program jest układany i modyfikowany doraźnie, przetestowanie i przebudowanie kodu interpretowanego jest prostsze; wreszcie, interpreter umożliwia pracę w trybie konwersacyjnym, wykonując polecenia podawane na bieżąco przez operatora, co poszerza zakres możliwych zastosowań.
Ceną prostoty jest wielokrotnie (nawet kilkaset razy) wolniejsza praca programów interpretowanych w porównaniu z kodem skompilowanym.
W przeszłości poznaliśmy już co najmniej jedno środowisko interpretujące: był nim systemowy procesor poleceń (shell). Trybu interpretacji używa się także do wykonywania programów napisanych w językach skryptowych, takich jak Basic, Perl, Python czy PHP.
W niektórych współczesnych systemach interpretujących, takich jak
Perl czy Python,
mamy do czynienia z wstępnym etapem kompilacji. Kompilator generuje
wtedy kod binarny będący przekształceniem oryginalnego kodu źródłowego.
Kod ten, poddawany wykonaniu w środowisku maszyny wirtualnej (patrz następny podrozdział),
wymaga przetłumaczenia na kod maszynowy. Dzieje się to przy każdym uruchomieniu;
dlatego termin interpretacja
pozostaje poprawny.
W tworzonych współcześnie platformach wykonawczych mamy często do czynienia z tzw. maszynami wirtualnymi. Maszyna taka jest programem, zapewniającym określone właściwości środowiska wykonawczego w taki sposób, by można je było traktować jak właściwości sprzętu. Dzięki temu program w postaci wykonywalnej dla maszyny wirtualnej działa w taki sam sposób na wszystkich platformach sprzętowych (typ procesora) i operacyjnych (system operacyjny). Zatem raz skompilowany program źródłowy można przenosić z miejsca na miejsce bez ograniczeń technicznych związanych ze sprzętem i oprogramowaniem systemowym — jedyne wymaganie dotyczy istnienia odpowiedniej maszyny wirtualnej. Takie właściwości są charakterystyczne m.in. dla języka Java oraz dla środowiska .NET.
plik z kompletnym kodem źródłowym (np. *.java, *.py) | | kompilacja (wykonywana jeden raz) ↓ plik z pośrednim kodem binarnym (np. *.class, *.pyc) -- program wykonywalny dla maszyny wirtualnej | | uruchamianie (wiele razy, w różnych maszynach wirtualnych, w różnych systemach operacyjnych) ↓
Istnieje również zaawansowana technika kompilacji w trakcie wykonywania programu, zwana just-in-time (JIT). Polega ona na generowaniu kodu binarnego w momencie, kiedy dany fragment programu jest wykorzystywany po raz pierwszy w danym uruchomieniu. Następne przypadki użycia danej funkcji będą już korzystać z efektów kompilacji, co znacznie przyspieszy wykonywanie programu.
Na ćwiczeniach będziemy mieć do czynienia z wybranymi algorytmami przetwarzania dyskretnego i numerycznego. Będziemy zgłębiać zasady ich działania, konstruować je samodzielnie, przetwarzać dane według opisanych za ich pomocą reguł — samodzielnie oraz za pośrednictwem dostępnego sprzętu obliczeniowego.
Algorytmy te posłużą jako podstawa do tworzenia kodu źródłowego programów w wybranym języku programowania wysokiego poziomu. Kod programu będzie przechowywany w pliku tekstowym, takim jak w zamieszczonych niżej przykładach.
Podstawowym językiem wykorzystywanym podczas kursu będzie Python. W laboratoriach zainstalowane jest oprogramowanie pozwalające uruchamiać kod źródłowy utworzony w tym języku.
Rozwiązywanie równania algebraicznego | |
Sumowanie ciągu liczb |
Proszę o sprawdzenie, że przedstawione programy realizują te same algorytmy, które wcześniej w podrozdziale 1.3.2. opisano za pomocą sieci działań.
Uruchomienie kodu Pythona jest proste. Jeden z elementarnych sposobów polega na wpisaniu polecenia
python rkw.py
do konsoli systemowej. W omawianym przypadku plik źródłowy rkw.py
powinien znajdować się w bieżącej kartotece. Jeżeli tak nie jest, trzeba podać
ścieżkę dostępu do niego.
W praktyce do tworzenia programów w języku Python przydatne jest oprogramowanie pomocnicze. Zostanie ono przedstawione w dalszym toku opracowania.
Uruchomienie przykładów napisanych w innych językach jest również możliwe. Chętnych odsyłamy do podręczników programowania w odpowiednim języku; wskazówki można też otrzymać podczas konsultacji.
W systemach informatycznych przeznaczonych do uruchamiania kodu źródłowego, stanowiącemu ich zasadniczy składnik kompilatorowi lub interpreterowi zazwyczaj towarzyszy środowisko robocze, wyposażone w edytor i oprogramowanie pomocnicze. Pełni ono kilka funkcji:
Użycie systemu wyposażonego w takie środowisko nie jest niezbędne, chociaż w wielu sytuacjach pozwala na bardzo efektywną pracę.
Wgląd w narzędzia i środowiska programistyczne, pozwalające na tworzenie, uruchamianie i dogłębne testowanie kodu źródłowego oraz na generowanie z niego programów wykonywalnych daje poświęcona temu tematowi galeria ilustracji.