D/Więcej o kompilowaniu

< D

Kompilator

edytuj

Kompilator 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

edytuj

Na 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

edytuj

Linker łą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

edytuj

Program 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

edytuj

Wybó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ń.