Podręcznik
Wersja podręcznika: 1.0
Data publikacji: 01.01.2022 r.
3. Kod źródłowy, maszynowy i kompilator
Po napisaniu aplikacji, która rozwiązuje określony problem, wykonywane są czynności polegające na przekształceniu kodu aplikacji na kod zrozumiały dla komputera. Kompilacja aplikacji to proces przekształcenia kodu źródłowego aplikacji na język programowania, na kod wykonywalny, który może być uruchomiony na konkretnym systemie komputerowym. Podczas kompilacji, kompilator (narzędzie programistyczne) analizuje kod źródłowy aplikacji i tłumaczy go na kod maszynowy, który jest zrozumiały dla komputera. Ten proces obejmuje kilka etapów, takich jak preprocesowanie, kompilację właściwą i linkowanie. W wyniku kompilacji uzyskuje się plik wykonywalny, który zawiera instrukcje, jakie należy wykonać, aby program działał zgodnie z oczekiwaniami. Kompilacja jest kluczowym krokiem w procesie tworzenia oprogramowania, ponieważ przekształca abstrakcyjny kod źródłowy napisany przez programistę na coś, co komputer jest w stanie zrozumieć i wykonać.
Kompilacja aplikacji w języku C++ składa się z kilku etapów, które prowadzą od kodu źródłowego do gotowego programu. Oto omówienie poszczególnych etapów kompilacji w języku C++:
Preprocesor: Pierwszym etapem kompilacji jest preprocesor. Preprocesor wykonuje różne manipulacje na kodzie źródłowym przed właściwą kompilacją. Na przykład, w tym etapie są wykonywane operacje takie jak włączanie plików nagłówkowych (directive
#include
), zastępowanie makr preprocesora (directive#define
), czy usuwanie komentarzy. Wynikiem tego etapu jest kod źródłowy po przetworzeniu przez preprocesor.Kompilacja: W tym etapie kod źródłowy jest przekształcany na kod maszynowy dla konkretnej platformy sprzętowej. Kompilator C++ tłumaczy kod źródłowy na język asemblera, który jest już bardziej zrozumiały dla komputera. Kompilator również sprawdza poprawność składniową i semantyczną kodu, zgłaszając błędy, jeśli są jakieś problemy.
Linkowanie: Po kompilacji kodu źródłowego każdy plik źródłowy może mieć swoje funkcje i zmienne. Linkowanie polega na połączeniu tych funkcji i zmiennych w jedną spójną aplikację lub bibliotekę. W tym etapie linkera są również rozwiązane wszystkie odwołania do funkcji i zmiennych zdefiniowanych w innych plikach lub bibliotekach.
Optymalizacja: Niektóre kompilatory wykonują również etap optymalizacji, w którym kod maszynowy jest analizowany i przekształcany w bardziej wydajną formę. Optymalizacje mogą obejmować m.in. eliminację zbędnych instrukcji, minimalizację zużycia pamięci, czy też przyspieszenie działania kodu poprzez zmniejszenie liczby operacji.
Po przejściu przez te etapy, gotowy program jest gotowy do uruchomienia na określonej platformie sprzętowej. Proces kompilacji w języku C++ jest złożony, ale dzięki temu możliwe jest tworzenie wydajnych i stabilnych aplikacji komputerowych.
Często mówiąc o wykonaniu programu źródłowego mamy na myśli oba te procesy kolejno: kompilację, a potem wykonanie.
Każdy z tych procesów może prowadzić do błędów. Możemy więc mówić o dwu rodzajach błędów, które mogą się nam przytrafić, a ściślej komputerowi, który próbuje sobie poradzić z naszym programem:
Błędy wykonania są znacznie trudniejsze do wykrycia, niż błędy kompilacji. Najtrudniejsze zaś do wykrycia są błędy, które w ogóle nie są sygnalizowane przez kompilator, czyli:
Kod maszynowy jest najniższym poziomem abstrakcji w programowaniu komputerów i jest bezpośrednio wykonywany przez procesor. W odróżnieniu od kodu źródłowego, który jest czytelny dla człowieka, kod maszynowy jest trudny do zrozumienia i pisania bezpośrednio przez programistę.
Kod maszynowy jest wykonywany na procesorze poprzez serię kroków, które obejmują pobieranie, dekodowanie i wykonanie instrukcji.
Pobieranie instrukcji: Procesor pobiera kolejne instrukcje z pamięci, zazwyczaj zlokalizowanej w pamięci RAM lub w pamięci podręcznej procesora (ang. cache). Adres kolejnej instrukcji jest przechowywany w specjalnym rejestrze zw. wskaźnikiem instrukcji (ang. Instruction Pointer).
Dekodowanie instrukcji: Następnie procesor dekoduje pobraną instrukcję. Oznacza to rozpoznanie, jaką operację należy wykonać (np. dodawanie dwóch liczb, przeniesienie danych z jednego miejsca do drugiego) oraz jakie dane są potrzebne do wykonania tej operacji.
Wykonanie instrukcji: Po dekodowaniu instrukcji, procesor wykonuje odpowiednią operację na danych, zgodnie z zrozumieniem instrukcji. Operacje te obejmują m.in. manipulację danymi w rejestrach procesora, dostęp do pamięci, wykonanie obliczeń matematycznych itp.
Aktualizacja stanu procesora: Po wykonaniu instrukcji, procesor może zaktualizować swój stan w zależności od efektów tej operacji. Na przykład, jeśli wykonano operację arytmetyczną, to wynik może być zapisany w określonym rejestrze. Jeśli operacja spowodowała zmianę warunków, np. ustawienie flagi wynikowej, procesor może odpowiednio zmodyfikować swoje flagi stanu.
Przejście do kolejnej instrukcji: Procesor przechodzi do kolejnej instrukcji, kontynuując wykonywanie programu, aż do osiągnięcia punktu zatrzymania, błędu, lub zakończenia programu.
Ten cykl pobierania, dekodowania i wykonania instrukcji jest kontynuowany, aż do zakończenia działania programu. Procesor wykonuje te kroki bardzo szybko, co umożliwia płynne działanie programów komputerowych.