D/Więcej o kompilowaniu
Kompilator
edytujKompilator jest narzędziem pozwalającym na „przetłumaczenie” kodu źródłowego programu na kod maszynowy zrozumiały dla procesora. Proces budowania programu możemy podzielić na dwa etapy:
- Faktyczna kompilacja - na podstawie kodu źródłowego programu tworzy się tzw. plik pośredni. Zawiera on jeszcze nazwy zmiennych, funkcji itd., nie można go jeszcze uruchomić.
- Linkowanie - na tym etapie łączone są pliki .obj/.o i biblioteki (głównie pliki .a), najważniejszą biblioteką w programach D jest Phobos. Usuwane są też zrozumiałe dla człowieka nazwy funkcji i zmiennych, powstały plik można już uruchomić.
W przypadku kompilatora dmd cały proces może być przeprowadzony na raz i programista nie musi osobno kompilować i linkować programu.
Kompilacja
edytujNa etapie kompilacji, pomijając optymalizacje itp., wszystko sprowadza się do przepisania kodu D na język assembly (1 mnemonik - jeden rozkaz procesora) i kompilacji powstałego kodu assemblerem, czyli kompilatorem języka assembly. Jednak tak jak wspomniano kod ten nie zawiera wielu informacji niezbędnych do poprawnego uruchomienia i dlatego ważne jest też linkowanie.
Linkowanie
edytujLinker łączy otrzymane pliki i zmienia nazwy funkcji i zmiennych w adresy, przykładowo jeśli w pliku main.d znajduje się wywołanie funkcji szczekaj zadeklarowanej jako extern void szczekaj(); linker przeszuka inne pliki w celu znalezienia definicji (ciała) funkcji szczekaj i zmieni wywołanie w skok pod odpowiedni adres w pamięci. Pomijając oczywiście magię z ramką stosu itd.
Makefile
edytujProgram make, korzystając z plików Makefile znacznie ułatwia proces budowania. Przy mniejszych projektach D jest raczej mało użyteczny, ponieważ większość można załatwić jedną komendą „dmd …” za przykład posłuży mi sytuacja w której chcę połączyć fragmenty w C++ i D.
// main.d
/* Informacja o istnieniu takiej funkcji dla linkera */
extern(C++) void szczekaj();
void main(string[] args)
{
/* Zwykłe wywołanie */
szczekaj();
}
// szczekaj.cpp
/* Nagłówek z std::cout */
#include <iostream>
void szczekaj()
{
/* Wypisanie na ekran „Hau hau!”, przejście do następnej linii i opróżnienie bufora */
std::cout << "Hau hau!" << std::endl;
}
Jak wyglądałaby kompilacja bez Makefile?
#build.sh
#! /bin/sh
#Utworzenie katalogu obj
mkdir obj
#Przłącznik -c oznacza samą kompilację, -of zmienia nazwę pliku wyjściowego
dmd -c main.d -ofobj/main.o
#-c jak wyżej, -o zamiast -of
g++ -c szczekaj.cpp -o obj/szczekaj.o
#dmd przejmuje funkcję linkera, -L pozwala na linkowanie wybranych biblitek
dmd obj/main.o obj/szczekaj.o -L-lstdc++ -ofmain
#Usunięcie katalogu na pliki pośrednie
rm -rf obj
Makefile działa na trochę innych zasadach, definiujemy w nim reguły wykonujące określone zadania. Pierwsza reguła jest domyślna, ale można wykonać konkretną z nich, np. polecenie „make katalogi” utworzy katalog obj.
#reguła „szczekaj” wymaga wykonania reguł „katalogi” i „obiekty”
szczekaj: katalogi obiekty
#linkowanie
dmd obj/main.o obj/szczekaj.o -L-lstdc++ -ofmain
#usuwanie plików pośrednich
rm -rf obj
#reguła „katalogi”
katalogi:
#utworzenie katalogu obj
mkdir obj
#reguła obiekty
obiekty:
#kompilacja osobnych plików, opisana wcześniej
dmd -c main.d -ofobj/main.o
g++ -c szczekaj.cpp -o obj/szczekaj.o
Więcej o make tu.
Podsumowanie
edytujWybór między skryptem .sh i Makefile jest sprawą indywidualną, jednak to drugie z reguły sprawdza się w większych projektach z uwagi na wymuszenie uporządkowania poleceń.