Wszystkie zagadnienia: Teoria
podstawowy teoria programowaniakompilatorinterpreterkod maszynowykod pośrednimatura podstawowa z informatyki

Od kodu źródłowego do maszynowego: kompilator, interpreter, kod pośredni

Kod źródłowy, maszynowy i pośredni oraz dwie drogi tłumaczenia: kompilacja całości przed uruchomieniem i interpretacja linia po linii.

01

Opis

Ten sam program może istnieć w kilku postaciach, coraz bardziej oddalonych od człowieka, a coraz bliższych procesorowi.

Kod źródłowy to tekst napisany przez programistę w języku wysokiego poziomu. Jest czytelny, można go poprawiać i to on podlega ochronie jako utwór. Dostęp do niego decyduje o tym, czy program jest otwarty.

Kod maszynowy to ciąg liczb binarnych, które procesor wykonuje bezpośrednio. Jest nieczytelny dla człowieka i związany z konkretną architekturą procesora. Blisko niego stoi asembler – zapis tych samych instrukcji przy pomocy skrótów słownych, czytelny na tyle, by dało się w nim pracować.

Kod pośredni leży pomiędzy. Nie jest już tekstem źródłowym, ale też nie jest instrukcjami konkretnego procesora – to uniwersalna forma wykonywana przez maszynę wirtualną. Dzięki temu jeden skompilowany plik działa na różnych systemach. Tak działa Java.

Procesor rozumie wyłącznie kod maszynowy, a człowiek pisze w języku programowania. Ktoś musi to przetłumaczyć – i tu pojawiają się dwa różne podejścia: kompilator i interpreter.

Cztery zasady wystarczą, żeby odpowiedzieć na większość pytań o drogę kodu:

  • Procesor wykonuje wyłącznie kod maszynowy. Tekst programu nie jest dla niego niczym więcej niż ciągiem znaków. Zanim cokolwiek się wykona, ktoś musi ten tekst przełożyć na rozkazy, które procesor zna – i tym kimś jest program tłumaczący, nie system i nie edytor.
  • Tłumaczenie odbywa się zawsze, pytanie tylko kiedy. Kompilator robi je raz, przed uruchomieniem, i zapisuje wynik na dysku. Interpreter robi je za każdym uruchomieniem, w trakcie działania programu. Nie ma trzeciej możliwości „program po prostu działa" – zawsze coś go przetłumaczyło.
  • Kod maszynowy jest przywiązany do procesora i do systemu. Plik wykonywalny zawiera rozkazy jednej rodziny procesorów i wywołania jednego systemu operacyjnego. Ten sam kod źródłowy da się skompilować dla innej platformy, ale gotowy plik już się nie przeniesie.
  • Droga w dół jest łatwa, w górę prawie niemożliwa. Z kodu źródłowego zawsze powstanie kod maszynowy. Z kodu maszynowego nie odtworzy się oryginału: nazwy zmiennych, komentarze i podział na funkcje przepadają w tłumaczeniu i nikt ich nigdzie nie zapisuje.

To hasło mówi o tłumaczeniu kodu, a nie o stylu jego pisania – podział na programowanie proceduralne, obiektowe i funkcyjne opisuje hasło o paradygmatach programowania.

02

Jak to działa

Kompilator tłumaczy cały program naraz, jeszcze przed uruchomieniem, i tworzy plik wykonywalny. Od tego momentu program działa samodzielnie, bez kompilatora. Jest szybki, bo tłumaczenie już się odbyło, a błędy składniowe wychodzą przed uruchomieniem. Wadą jest to, że plik wykonywalny działa tylko na tej platformie, dla której powstał, a po każdej zmianie trzeba kompilować od nowa.

Interpreter tłumaczy i wykonuje kod linia po linii w trakcie działania. Nie powstaje plik wykonywalny – do uruchomienia programu potrzebny jest interpreter. Jest wolniejszy, bo tłumaczenie odbywa się na bieżąco, ale za to ten sam kod działa wszędzie, gdzie zainstalowano interpreter, a efekt zmiany widać natychmiast.

Języki takie jak C i C++ są kompilowane, a Python jest interpretowany – dlatego program w Pythonie uruchamia się bez osobnego kroku kompilacji. Rozwiązaniem pośrednim jest kompilacja do kodu pośredniego: program kompiluje się do postaci uniwersalnej, a maszyna wirtualna wykonuje ją na docelowym sprzęcie.

Kompilacja większego programu ma jeszcze jeden krok, o którym łatwo zapomnieć, a który bywa w zadaniach czwartą odpowiedzią do odrzucenia. Kompilator przerabia każdy plik źródłowy osobno i zostawia po nim plik z kodem maszynowym, ale jeszcze nie gotowy do uruchomienia. Dopiero konsolidator zbiera te pliki razem z bibliotekami – gotowym kodem, którego nikt nie pisał na nowo, na przykład z procedurami wypisywania na ekran – i skleja je w jeden plik wykonywalny. Konsolidator niczego nie tłumaczy: łączy to, co kompilator już przetłumaczył. Dlatego w zadaniu, w którym pyta się o tłumaczenie, konsolidator jest odpowiedzią błędną.

Warto też widzieć różnicę między tłumaczeniem a uruchomieniem. Gdy plik wykonywalny jest już gotowy, tłumacz nie jest do niczego potrzebny – wczytaniem pliku do pamięci i oddaniem procesorowi czasu na jego rozkazy zajmuje się system operacyjny. To dlatego skompilowany program jest przywiązany nie tylko do rodziny procesorów, ale i do systemu: w środku ma wywołania funkcji tego konkretnego systemu, a inny system ich nie rozpozna, nawet jeśli procesor jest identyczny.

Moment, w którym wychodzi błąd, jest praktycznie najważniejszą różnicą między obiema drogami. Kompilator czyta cały tekst programu, zanim cokolwiek powstanie, więc literówka w rzadko odwiedzanej linii zatrzyma budowanie pliku wykonywalnego – program ani razu się nie uruchomi. Interpreter dochodzi do tej samej linii dopiero wtedy, gdy wykonanie tam trafi. Wcześniejsze instrukcje wykonają się normalnie i zdążą zrobić swoje: wypisać tekst, zapisać plik, zmienić dane. Program przerwie się w połowie pracy, zostawiając po sobie skutki – i to jest sytuacja, której przy kompilacji po prostu nie ma.

03

Wizualizacja

Tabela wejściowa
PlikJęzyk i sposób tłumaczeniaDługośćGdzie jest błąd
program.cppC++, kompilowany60 liniilinia 40: literówka w nazwie zmiennej
program.pyPython, interpretowany60 liniilinia 40: literówka w nazwie zmiennej
→
Wynik
EtapKompilacja: program.cppInterpretacja: program.py
Kiedy zaczyna się tłumaczenieprzed uruchomieniem, na komputerze programistydopiero w chwili uruchomienia, linia po linii
Co powstaje na dyskuosobny plik wykonywalny, na przykład program.exenic nowego, zostaje sam plik program.py
Kiedy wychodzi błąd z linii 40przed uruchomieniem: kompilator przerywa pracę i nie tworzy pliku wykonywalnegow trakcie działania, po poprawnym wykonaniu 39 wcześniejszych linii
Co użytkownik zdąży zobaczyćnic, bo program nie uruchomił się ani razuwyniki 39 linii: wypisany tekst, zapisane pliki, zmienione dane
Co dostaje odbiorca programusam plik wykonywalny, bez kodu źródłowegokod źródłowy, bo bez niego nie ma czego uruchomić
Co musi mieć docelowy komputersystem i procesor zgodne z tymi, dla których skompilowanozainstalowany interpreter tego języka
Co trzeba zrobić po poprawceskompilować od nowa i rozesłać nowy plik wykonywalnyzapisać plik i uruchomić program ponownie
04

Tabela

Etapy drogi od kodu źródłowego do działającego programu
EtapCo wchodziCo wychodziCzym się to robi
Napisanie programupomysł i specyfikacja zadaniakod źródłowy w pliku tekstowymedytor kodu lub środowisko programistyczne
Sprawdzenie składnikod źródłowylista błędów albo zgoda na dalsze etapykompilator, jego pierwsza faza
Tłumaczenie całości z górypoprawny składniowo kod źródłowykod maszynowy w plikach pośrednichkompilator
Dołączenie bibliotekpliki z kodem maszynowym i gotowe bibliotekijeden plik wykonywalnykonsolidator
Uruchomienieplik wykonywalny na dyskuprogram wczytany do pamięci operacyjnejsystem operacyjny
Wykonanie rozkazówkod maszynowy w pamięciwyniki działania programuprocesor
Tłumaczenie na bieżącokod źródłowy czytany linia po liniirozkazy wykonywane od razu, bez zapisu na dyskinterpreter
Kompilacja do kodu pośredniegokod źródłowykod pośredni, niezależny od rodziny procesorówkompilator danego języka
Wykonanie kodu pośredniegokod pośredniwyniki działania programumaszyna wirtualna
Tłumaczenie zapisu bliskiego sprzętowiprogram w asemblerzekod maszynowyasembler, czyli program tłumaczący
Próba odwrócenia drogiplik wykonywalnykod przybliżony, bez nazw zmiennych i komentarzydekompilator
05

Porównanie

CechaKompilatorInterpreter
Moment tłumaczeniaprzed uruchomieniemw trakcie działania
Ile razy tłumaczyraz, przy budowaniu programuprzy każdym uruchomieniu od nowa
Wynik na dyskuplik wykonywalnyżaden, zostaje kod źródłowy
Szybkość działaniawiększamniejsza
Wykrywanie błędów składniprzed uruchomieniem, w całym plikudopiero po dojściu wykonania do błędu
Co widać po błędzie w środku programunic, program się nie uruchomiłskutki wszystkich wcześniejszych linii
Co musi mieć docelowy komputerzgodny system i procesorzainstalowany interpreter
Co dostaje odbiorcaprogram bez kodu źródłowegokod źródłowy, bo on jest programem
Przenośnośćograniczona do jednej platformywysoka
PrzykładyC, C++Python
Nazwa angielskacompilerinterpreter
06

Typowe błędy

Zmiana rozszerzenia pliku na exe. To najbardziej pouczający błąd w całym haśle, bo CKE sprawdziła go wprost: w kluczu z 2011 roku zdanie „wystarczy zmienić rozszerzenie głównego pliku tego programu na exe" jest fałszywe. Rozszerzenie to tylko końcówka nazwy pliku, informacja dla systemu, czym plik ma być otwierany. Nie zmienia ani jednego bajtu zawartości. Plik tekstowy nazwany program.exe nadal zawiera litery, a nie rozkazy procesora, więc próba uruchomienia skończy się komunikatem o błędzie. Kod maszynowy powstaje wyłącznie przez tłumaczenie.

Kompilator pomylony z edytorem, BIOS-em albo innym narzędziem. Klucze z 2017 i 2009 roku zbudowane są dokładnie na tym nieporozumieniu i odrzucają kolejno walidator, edytor tekstu, defragmentator, BIOS i konsolidator. Edytor służy do pisania kodu i niczego nie tłumaczy – w środowisku programistycznym siedzi obok kompilatora w jednym oknie, przez co łatwo je utożsamić. BIOS jest oprogramowaniem uruchamiającym komputer i należy do hasła o budowie komputera; z językami programowania nie ma nic wspólnego. Konsolidator, jak wyżej, łączy, a nie tłumaczy. Tłumaczy tylko kompilator, interpreter i asembler.

Przekonanie, że plik wykonywalny z Windowsa uruchomi się na Linuksie. Skoro procesor jest ten sam, wydaje się, że plik powinien zadziałać. Nie zadziała, bo skompilowany program to nie tylko rozkazy procesora – to także wywołania funkcji konkretnego systemu operacyjnego: otwórz plik, narysuj okno, przydziel pamięć. Każdy system nazywa te funkcje po swojemu. Przenieść można kod źródłowy i skompilować go osobno na drugim systemie; gotowego pliku wykonywalnego przenieść się nie da. Odwrotnie jest z programem interpretowanym: on jest kodem źródłowym, więc wystarczy interpreter po drugiej stronie.

Przekonanie, że z pliku wykonywalnego odzyska się kod źródłowy. Tłumaczenie jest stratne w jednym konkretnym sensie: nazwy zmiennych, nazwy funkcji, komentarze i puste linie nie są procesorowi do niczego potrzebne, więc w pliku wynikowym po prostu ich nie ma. Dekompilator odtworzy strukturę obliczeń, ale nazwie zmienne po swojemu i nie wymyśli komentarzy. Dostaje się coś, co robi to samo, a czyta się nieporównanie gorzej niż oryginał. To dlatego program rozpowszechniany w postaci skompilowanej chroni pomysł autora, a licencja typu GPL wymaga udostępnienia właśnie kodu źródłowego, nie samego pliku wykonywalnego.

Interpreter rozumiany jak tłumacz języka naturalnego. Polska nazwa „tłumaczenie" podpowiada obraz translatora, który dostaje cały tekst i oddaje cały przekład. Interpreter tak nie pracuje: bierze jedną instrukcję, od razu ją wykonuje, dopiero potem sięga po następną. Stąd wynikają wszystkie jego właściwości – brak pliku wynikowego, mniejsza szybkość i to, że o błędzie w czterdziestej linii dowiadujemy się po wykonaniu trzydziestu dziewięciu poprzednich. Kompilator jest bliższy potocznemu „tłumaczeniu": czyta całość, zanim cokolwiek odda.

07

Zapamiętaj

  • Kod źródłowy pisze człowiek; procesor go nie rozumie i wykonuje wyłącznie kod maszynowy.
  • Tłumaczenie odbywa się zawsze – kompilator robi je raz przed uruchomieniem, interpreter przy każdym uruchomieniu, linia po linii.
  • Kompilacja daje plik wykonywalny działający bez kompilatora; program interpretowany bez interpretera nie ruszy.
  • Błąd składni kompilator zgłasza przed uruchomieniem, interpreter dopiero po dojściu wykonania do tej linii – wcześniejsze instrukcje zdążą się wykonać.
  • Kod maszynowy zależy od rodziny procesorów i od systemu, więc pliku wykonywalnego nie przeniesie się między systemami; kod źródłowy przenosi się swobodnie.
  • Zmiana rozszerzenia pliku na exe niczego nie kompiluje – to tylko inna nazwa tego samego pliku.
  • Konsolidator łączy pliki z kodem maszynowym i biblioteki w jeden plik wykonywalny, ale nie tłumaczy; kod pośredni wykonuje maszyna wirtualna.
08

Sprawdź się

W jednym katalogu leżą cztery pliki tego samego projektu:

PlikCo zawieraJak trafia do procesora
projekt.cppkod źródłowy w C++przez kompilację przed uruchomieniem
projekt.pykod źródłowy w Pythonieprzez interpretację w trakcie działania
projekt.exekod maszynowy dla Windowsajest wykonywany bezpośrednio
projekt.asmprogram zapisany w asemblerzepo przetłumaczeniu na kod maszynowy

Uzupełnij odpowiedzi:

PytanieOdpowiedź
Która postać programu jest wykonywana bezpośrednio przez procesor?
Program tłumaczący cały kod przed uruchomieniem to
Program tłumaczący i wykonujący kod linia po linii to
W pliku projekt.py błąd składni jest w 40. linii. Ile wcześniejszych linii wykona się poprawnie?
Ile linii wykona się w projekt.cpp, zanim kompilator zgłosi ten sam błąd?
Który z czterech plików uruchomi się bez zainstalowanego interpretera?
Program łączący pliki z kodem maszynowym i biblioteki w jeden plik wykonywalny to
Co wykonuje kod pośredni?

Po kompilacji odbiorca dostaje i nie potrzebuje już kompilatora, natomiast program interpretowany rozsyła się w postaci , bo bez niego nie ma czego uruchomić.

09

Najczęściej zadawane pytania

Co jest szybsze i dlaczego?

Kod skompilowany, bo tłumaczenie odbyło się wcześniej. Interpreter musi analizować instrukcje na bieżąco, co kosztuje czas przy każdym uruchomieniu.

Dlaczego program napisany na Windowsie nie działa na Linuksie?

Bo kompilacja tworzy plik dla konkretnego systemu i architektury. Ten sam kod źródłowy trzeba skompilować osobno dla każdej platformy.

Dlaczego kod maszynowy jest zależny od procesora?

Bo każda architektura ma własny zestaw rozkazów. Instrukcje zapisane dla jednej rodziny procesorów nie mają sensu dla innej.

Czy da się odzyskać kod źródłowy z pliku wykonywalnego?

Dekompilacja pozwala odtworzyć coś zbliżonego, ale bez nazw zmiennych i komentarzy. Efekt bywa trudny do zrozumienia i nie jest oryginałem.

Po co komu asembler?

Do zadań wymagających pełnej kontroli nad sprzętem i maksymalnej wydajności – sterowników i systemów wbudowanych. W typowym programowaniu jest zbędny.

Czym zajmuje się konsolidator?

Zbiera pliki z kodem maszynowym, które wyprodukował kompilator, dokłada do nich biblioteki i skleja wszystko w jeden plik wykonywalny. Sam niczego nie tłumaczy, dlatego w zadaniu o tłumaczeniu kodu jest odpowiedzią błędną – tak właśnie użyto go w kluczu z 2009 roku.

Czy Python jest zawsze interpretowany?

Standardowy Python tłumaczy kod źródłowy do własnego kodu pośredniego i wykonuje go maszyną wirtualną – z punktu widzenia użytkownika działa jak interpreter: bez osobnego kroku kompilacji i bez pliku wykonywalnego. Na maturze obowiązuje klasyfikacja „Python – interpretowany".

Przećwicz w zadaniach maturalnych

Do tego hasła nie mamy jeszcze przypisanych pojedynczych zadań. Cały spis zadań maturalnych z tego działu jest tutaj:

Zadania z działu: Teoretyczne

↗

Powiązane zagadnienia