Definicja i cel kompilatora
ANDRZEJ WOJCICKI / Getty Images
A kompilator jest program który tłumaczy się czytelnie dla człowieka kod źródłowy na wykonywalny komputerowo kod maszynowy. Aby to zrobić pomyślnie, kod czytelny dla człowieka musi być zgodny z składnia reguły dowolnego języka programowania, w którym jest napisany. Kompilator jest tylko programem i nie może naprawić kodu za Ciebie. Jeśli popełnisz błąd, musisz poprawić składnię, bo inaczej się nie skompiluje.
Co się dzieje, gdy kompilujesz kod?
Złożoność kompilatora zależy od składni języka i ilości abstrakcji ten język programowania zapewnia. Kompilator C jest znacznie prostszy niż kompilator C++ lub C#.
Analiza leksykalna
Podczas kompilacji kompilator najpierw odczytuje strumień znaków z pliku kodu źródłowego i generuje strumień leksykalnych tokenów. Na przykład kod C++:
|_+_||_+_|mogą być analizowane jako te tokeny:
- wpisz „int”
- zmienna „C”
- równa się
- lewy nawias klamrowy
- zmienna „A”
- czasy
- zmienna „B”
- prawy nawias klamrowy
- plus
- dosłowne „10”
Analiza syntaktyczna
Dane wyjściowe leksykalne trafiają do części kompilatora dotyczącej analizatora składniowego, który używa reguł gramatyki, aby zdecydować, czy dane wejściowe są prawidłowe, czy nie. Chyba że zmienne A i B zostały wcześniej zadeklarowane i znajdują się w zakresie, kompilator może powiedzieć:
- „A” : niezadeklarowany identyfikator.
Jeśli zostały zadeklarowane, ale nie zostały zainicjowane. kompilator wyświetla ostrzeżenie:
- zmienna lokalna „A” używana bez zainicjowania.
Nigdy nie powinieneś ignorować ostrzeżeń kompilatora. Mogą złamać Twój kod w dziwny i nieoczekiwany sposób. Zawsze naprawiaj ostrzeżenia kompilatora.
Jeden przejazd czy dwa?
Niektóre języki programowania są tak napisane, że kompilator może tylko raz odczytać kod źródłowy i wygenerować kod maszynowy. Pascal jest jednym z takich języków. Wiele kompilatory wymagają co najmniej dwóch przejść. Czasami dzieje się tak z powodu przedłożonych deklaracji Funkcje lub klas.
W C++ klasę można zadeklarować, ale zdefiniować dopiero później. Kompilator nie jest w stanie określić, ile pamięci potrzebuje klasa, dopóki nie skompiluje ciała klasy. Musi ponownie odczytać kod źródłowy przed wygenerowaniem poprawnego kodu maszynowego.
Generowanie kodu maszynowego
Zakładając, że kompilator pomyślnie zakończy analizy leksykalne i syntaktyczne, ostatnim etapem jest wygenerowanie kodu maszynowego. Jest to skomplikowany proces, szczególnie w przypadku nowoczesnych procesorów.
Szybkość kompilacji wykonywalny kod powinien być tak szybki, jak to możliwe i może się znacznie różnić w zależności od jakości wygenerowanego kodu i żądanej optymalizacji.
Większość kompilatorów umożliwia określenie ilości optymalizacji — zwykle znanej z szybkiego debugowania kompilacji i pełnej optymalizacji wydanego kodu.
Generowanie kodu jest wyzwaniem
Pisarz kompilatora napotyka wyzwania podczas pisania generatora kodu. Wiele procesorów przyspiesza przetwarzanie za pomocą
- Instrukcja rurociągów
- Wewnętrzny skrytki .
Jeśli wszystkie instrukcje w kodzie pętla może odbywać się w procesor cache, wtedy ta pętla działa znacznie szybciej niż wtedy, gdy procesor musi pobierać instrukcje z głównej pamięci RAM. Pamięć podręczna procesora to blok pamięci wbudowany w układ procesora, do którego dostęp uzyskuje się znacznie szybciej niż dane w głównej pamięci RAM.
Pamięć podręczna i kolejki
Większość procesorów ma kolejkę wstępnego pobierania, w której procesor odczytuje instrukcje do pamięci podręcznej przed ich wykonaniem. Jeśli wystąpi gałąź warunkowa, procesor musi ponownie załadować kolejkę. Kod powinien zostać wygenerowany, aby to zminimalizować.
Wiele procesorów ma oddzielne części dla:
- Arytmetyka liczb całkowitych (liczby całkowite)
- Arytmetyka zmiennoprzecinkowa (liczby ułamkowe)
Te operacje mogą często przebiegać równolegle, aby zwiększyć prędkość.
Kompilatory zazwyczaj generują kod maszynowy w plikach obiektowych, które następnie są połączony razem przez program łączący.