Kompilator GCC i program make

Kompilator GCC ma w swoim zestawie wiele różnych kompilatorów w tym także najważniejszy język C. Kompilowanie programu za pomocą GCC z reguły odbywa się dwu etapowo. Jest to najlepszy sposób przy dużych programach składających się z wielu plików. Najpierw pilki.c zostają zmieniane na pliki.o, czyli takie które rozumie komputer, a następnie z tych plików zostaje utworzony program.

Aby stworzyć pliki obiektowe należy użyć opcji "-c":

gcc -c nazwa_pliku.c

W ten sposób zostanie otworzony pliki:

nazwa_pliku.o

Jeśli do kompilacji używaliśmy plików nagłówkowych pliki_naglowkowe.h, który znajduje się w innym katalogu niż projekt, to należy podać ten katalog w wierszu poleceń. Nie musimy tego robić, gdy pliki z bibliotekami wrzucone są w tym samym katalogu co projekt, dodane do standardowych bibliotek lub ich link jest dodane do zmiennej PATH.

Ścieżkę pliku nagłówkowego można dodać za pomocą opcji "-I":

gcc -c –I katalog_naglowka nazwa_pliku.c

Mając już pliki.o można z nich stworzyć jeden program wykonywalny, czyli można je skonsolidować robi się to za pomocą opcji "-o".

gcc –o launch nazwa_pliku1.o nazwa_pliku2.o

W powyższym przypadku zostanie utworzony plik wykonywalny launch z dwóch plików obiektowych. Jeśli wszystkie pliki obiektowe znajdują się w tym samym katalogu to można użyć polecenia:

gcc *.o -o launch

W systemach Unix można połączyć proces scalania i kompilacji. Co nie oznacza, że będzie to trwało krócej. Komputer i tak musi stworzyć pliki obiektowe, ale nie zostają one zapisane na dysku. Można to zrobić poleceniem:

gcc –o launch nazwa_pliku1.c nazwa_pliku2.c

Jeśli nasz program korzysta z bibliotek statycznych, to należy je dodać przez opcje "-L", aby podać katalog biblioteki i "-l", aby podać nazwę biblioteki:

gcc –o launch nazwa_pliku1.c nazwa_pliku2.c -L nazwa_katalogu_biblioteki -l nazwa_biblioteki

To są podstawowe opcje kompilatora GCC. Oczywiście kompilator ma w zestawie wiele dodatkowych opcji takich jak -Wall, która może zwiększyć ilość ostrzeżeń. Jeśli chcemy, żeby generowane były wszystkie ostrzeżenia, to należy użyć opcji -Wextra:

gcc nazwa_pliku.c -Wall -Wextra -o launch

Najbardziej rygorystyczną opcją jest opcja -Werror, która zamienia wszystkie ostrzeżenia na errory.

Za pomocą kompilatora możemy zoptymalizować kod używając opcji "-O", albo skompilować program na odpowiedni procesor. W programach takich jak DEV-C++ wszystkie najważniejsze opcje dostarczone są w formie opcji w opcjach kompilatora. Dla przykładu pokazałem jak użyć opcji -Wall w programie DEV-C++:

Za pomocą opcji w projekcie można dostosować kompilator do własnych potrzeb. Można tam dodać parametry, zmienić opcje kompilacji i wiele innych rzeczy.

Drugim programem który jest praktycznie niezbędny do pracy z większą ilością plików jest program make.

Ale czym jest tak naprawdę program make?

Wyobraźmy sobie, że mamy kilkaset plików w projekcie. Aby wygenerować plik wykonywalny musimy je skompilować i połączyć w jeden plik. Tu z pomocą przychodzi opcja -c kompilatora, która utworzy pliki obiektowe. Najlepiej w tym przypadku najpierw stworzyć pliki obiektowe dla każdego pliku.c, a następnie kompilować tylko te pliki, które uległy zmianom. Sam proces konsolidacji (łączenia plików obiektowych) nie trwa tak długo jak kompilacja, dlatego w ten sposób zaoszczędzimy wiele czasu. Programy typu Make lub Ant właśnie do tego służą. Potrafią same zobaczyć, które pliki zostały zmodyfikowane i skompilować tylko te które tego wymagają, ai tym samym zaoszczędzić trochę czasu przy testowaniu kodu.

Program make działa w następujący sposób. Należy stworzyć plik makefile, który opisuje instrukcje do wykonania.

Struktury pliku makefile wygląda następująco:

nazwa_celu: zaleznosc zaleznosc ...
tabulator polecenie
nazwa_celu: zaleznosc zaleznosc ...
tabulator polecenie
....

Te instrukcje określają co ma się wykonać w odpowiedniej kolejności. Przykładowy zapis tych zależności zaprezentowałem poniżej:

prog.e: m1.o m2.o
 gcc –o prog.e m1.o m2.o
m1.o: m1.c m1.h
 gcc –c m1.c
m2.o: m2.c m1.h
 gcc –c m1.c
clean:
 rm *.o *.e

Te instrukcje zostaną wykonane podczas użycia programu make. Należy pamiętać, że w różnych systemach program make może nazywać się inaczej np. gmake (GNU make).

Aby użyć programu wystarczy wpisać w wierszu poleceń następującą regułę:

make -opcje nazwa_celu lub plik_wykonywalny

Opcje dostępne wypisałem poniżej, chociaż nie są dostępne dla każdej wersji programu make:

-n : polecenia są składane i wyświetlane, ale nie wykonywane (dobre do testów)
-I <katalog> : katalog do poszukiwania plików make poza katalogiem bieżącym (duże 'i')
-s : (silent) make nie wypisuje poleceń na ekranie
-f <plik> : nazwa pliku make inna niż „makefile” lub „Makefile”
-k : nie przerywa działania jeśli nie uda się zbudować jednego z celów
-d : (debug) wyświetlane są informacje debugowania
-W<plik> : wykonuje się tak jakby wymieniony plik był zmodyfikowany (do testowania)

Oczywiście środowiska programistyczne mają wbudowane takie mechanizmy i potrafią określić, które pliki mają zostać skompilowane.W środowisku DEV-C++ używany jest program make, który generuje plik makefile.win i można sobie go podejrzeć w katalogu projektu.

# Project: Projekt2
# Makefile created by Dev-C++ 5.4.2

CPP      = g++.exe
CC       = gcc.exe
WINDRES  = windres.exe
OBJ      = main.o
LINKOBJ  = main.o
LIBS     = -L"C:/Program Files/Dev-Cpp/MinGW64/x86_64-w64-mingw32/lib32" -static-libgcc -m32
INCS     = -I"C:/Program Files/Dev-Cpp/MinGW64/x86_64-w64-mingw32/include"
CXXINCS  = -I"C:/Program Files/Dev-Cpp/MinGW64/x86_64-w64-mingw32/include"
BIN      = 1.exe
CXXFLAGS = $(CXXINCS) -m32
CFLAGS   = $(INCS) -m32
RM       = rm -f

.PHONY: all all-before all-after clean clean-custom

all: all-before $(BIN) all-after


clean: clean-custom
 ${RM} $(OBJ) $(BIN)

$(BIN): $(OBJ)
 $(CC) $(LINKOBJ) -o $(BIN) $(LIBS)

main.o: main.c
 $(CC) -c main.c -o main.o $(CFLAGS)

Widzimy, że w tym przypadku najpierw zostają zapisane zmienne w postaci:

CC       = gcc.exe

Zmienne określają nam różne parametr takie jak kompilator, opcje itp. Czasem zmienne przypisuje się za pomocą operatora :=

CC       := gcc.exe

Aby użyć zmienne należy użyć operatora $ czyli dla przykładu trzeba napisać tak:

$(CC)

Te pliki generowane są automatycznie z poziomu środowiska programistycznego. Dlatego pliki makefile.win dla DEV-C++ wyglądają troszkę inaczej i używają wielu zmiennych. Możecie przetestować środowisko DEV-C++ i dodać jakiś parametr lub zmienić jakąś opcje w kompilatorze, a wszystko ukaże się w pliku makefile.

Dla przykładu dodając opcje -Wall w kompilatorze DEV-C++ zmieni się także plik makefile. -Wall zostanie dodane do parametru CFLAGS i CXXFLAGS W taki sposób:

CXXFLAGS = $(CXXINCS) -m32 -Wall
CFLAGS   = $(INCS) -m32 -Wall

Za pomocą operatora $ można dostać się do standardowych zmiennych, które mają własne znaki specjalne. Znaki zarezerwowane zaprezentowałem poniżej:

$@ -symboliczna nazwa pliku celu w regule
$* -rdzeń nazwy pliku (bez rozszerzenia po kropce)
$< -nazwa pliku pierwszej zależności od reguły
$^ -lista wszystkich zależności w regule
$? - lista zależności nowszych niż cel

Ja oczywiście nie zagłębiałem się w pliki makefile, bo z reguły korzystam z gotowego środowiska programistycznego, które robi to za mnie. Nie mniej fajnie wiedzieć skąd się to wszystko bierze.