Úvod do modulárneho programovania
nástroj make
, konfiguračný súbor
Makefile
Video pre cvičenie
Motivácia
V prvom zadaní, do ktorého sa tento týždeň pustíte, budete mať okrem
iného za úlohu vytvoriť dva samostatné moduly. Aby ste ich zvládli
vytvoriť a správne manažovať, naučíte sa na tomto cvičení konfigurovať
nástroj make
pomocou jeho konfiguračného súboru
Makefile
. Táto znalosť vám v budúcnosti pomôže pri práci na
rozsiahlejších projektoch, ktoré sa budú skladať z viacerých
samostatných modulov.
Ciele
- Naučiť sa vytvoriť vlastný konfiguračný súbor
Makefile
. - Naučiť sa vytvárať vlastné ciele (targets) v súbore
Makefile
. - Naučiť sa základy práce s nástrojom
cppcheck
. - Naučiť sa základy práce s nástrojom .
- Porozumieť významu modulárneho programovania.
Postup
Funkcia
reverse()
Aby sme nepracovali s prázdnym kódom, urobíme v tomto kroku naivnú
implementáciu funkcie reverse()
, ktorá patrí do modulu
bmp
. Jej implementáciu umiestnime do súboru
bmp.c
a odskúšame ju v súbore main.c
.
Úloha
V súbore bmp.c
vytvorte implementáciu funkcie
reverse()
.
Pre jednoduchosť vytvoríme veľmi naivnú implementáciu tejto funkcie. Naša implementácia bude vyzerať takto:
char* reverse(const char* text){
// returns reversed string "Hello world!"
return "!DLROW OLLEH";
}
Poznámka
Ak už máte vytvorenú vlastnú implementáciu funkcie
reverse()
, nemusíte v tomto kroku robiť nič. Budete
pracovať so svojou vlastnou implementáciou.
Úloha
Otestujte vytvorenú funkciu reverse()
v hlavnej funkcii
main()
v súbore main.c
.
Pre otestovanie môžete použiť napr. fragment kódu, ktorý sa nachádza
v opise funkcie reverse()
v špecifikácii zadania:
int main(){
char* reversed = reverse("Hello world!");
("%s\n", reversed);
printf// "!DLROW OLLEH"
}
Úloha
Preložte a spustite projekt.
Program sa pokúste preložiť pomocou prekladača gcc
spolu
so zoznamom všetkých prepínačov, ktoré sú uvedené na stránke zadania Top Secret. Výsledný
spustiteľný súbor nech sa volá jednoducho ps1
.
Úloha
Aktualizujte svoj projekt na GitLab-e.
Okrem aktualizovania lokálneho repozitára nezabudnite váš projekt odoslať aj do vzdialeného repozitára.
Konfiguračný súbor
Makefile
V tomto kroku si vytvoríte svoj vlastný súbor Makefile
,
ktorý povie nástroju make
čo má robiť. Súbor
Makefile
sa skladá zo zoznamu pravidiel, pomocou ktorých
nástroj make
preloží a zostaví výsledný program. Tento
súbor sa používa hlavne vtedy, ak pracujete na projekte, ktorého kód je
uložený vo viacerých súboroch, ako len v jednom.
Úloha
Vytvorte v súbore Makefile
pravidlo s názvom cieľa
ps1
, pomocou ktorého bude možné preložiť celý program.
Každé pravidlo v súbore Makefile
má nasledovnú
štruktúru:
target: dependencies
command
... command
Význam jednotlivých častí pravidla je nasledovný:
target
- Predstavuje názov pravidla (z angl. cieľa). Týmto názvom môže byť názov súboru, ktorý sa má vytvoriť (napr. výsledná spustiteľná binárka alebo objektový súbor), ale rovnako to môže byť len názov úlohy alebo akcie, ktorá sa má vykonať (napr. spustením úlohy s názvomclean
sa zmažú všetky preložené súbory). V prípade nášho prekladu pomocou prekladačagcc
to môže byť napr. názov výsledného spustiteľného programu, ktorým jeps1
.dependencies
- Zoznam súborov, na ktorých závisí zostavenie cieľa (alebo vykonanie pravidla). Tieto súbory musia existovať predtým, ako sa začnú vykonávať jednotlivé príkazy. V prípade nášho prekladu pomocou prekladačagcc
bude zoznam závislostí tvorený všetkými zdrojovými súbormi (s príponou.c
).commands
- Zoznam príkazov, ktoré sa vykonajú na zostavenie daného cieľa. V prípade nášho prekladu pomocou prekladačagcc
bude zoznam príkazov tvorený len jediným príkazom na preloženie a zostavenie výsledného spustiteľného súboru.
Poznámka
Existujú pravidlá, kde je zoznam príkazov prázdny. V našom prípade to
bude pravidlo s cieľom all
, ktoré si vytvoríme neskôr počas
cvičenia.
Upozornenie
Je veľmi dôležité, aby prvý znak pred každým príkazom (tzv.
oddeľovač, z angl. separator) bol znak tabulátor. Ak
sa tam bude nachádzať znak medzera, súbor Makefile
nebude
valídny a nástroj make
skončí s chybou.
Ak pre programovanie používate editor vim
a v jeho
konfigurácii je explicitne uvedená voľba set expandtab
na
nahradenie znaku tabulátor uvedeným počtom medzier, potrebujete
ju vypnúť. Pre otvorený súbor môžete túto voľbu potlačiť napísaním
príkazu v príkazovom režime:
:set noexpandtab
Výsledné pravidlo v súbore Makefile
bude vyzerať
nasledovne:
ps1: bmp.c main.c playfair.c
gcc -std=c11 -Wall -Werror playfair.c bmp.c main.c -lm -o ps1
Úloha
Overte funkčnosť vytvoreného cieľa.
Funkčnosť overíte spustením príkazu make
nasledovne:
$ make TARGET
kde TARGET
je názov cieľa, ktorý chcete spustiť.
Ak ste postupovali správne, po spustení príkazu make
dôjde k preloženiu programu a vytvoreniu spustiteľného súboru
ps1
.
Poznámka
Ak pri spúšťaní príkazu make
neuvediete žiadny cieľ,
spustí sa prvé pravidlo v poradí, ktoré sa v súbore
Makefile
nachádza.
Ak spustíte príkaz make
viackrát za sebou, môžete si
všimnúť, že nedôjde k opätovnému zostaveniu cieľa, pretože nedošlo k
zmene v závislostiach. Ak však v niektorom zo súborov so zdrojovým kódom
urobíte zmenu (stačí použiť len príkaz touch
), dôjde k
opätovnému prekladu.
Moduly a objektové súbory
Aktuálne nám vytvorené pravidlo umožňuje preložiť celý program naraz.
Pravidlo závisí na troch zdrojových súboroch, čo znamená, že výsledný
program je možné rozdeliť do troch modulov: main
,
playfair
a bmp
.
Každý z týchto modulov je možné preložiť samostatne, čím sa vždy vytvorí tzv. objektový súbor. Spojením objektových súborov a prilinkovaním potrebných knižníc nakoniec dôjde k vytvoreniu spustiteľného súboru.
Vytvoríme teda tri nové pravidlá s názvami main.o
,
playfair.o
a bmp.o
, kde v každom jednom
vytvoríme objektový súbor konkrétneho modulu programu. Pre vytvorenie
objektového súboru použijeme pri preklade prepínač -c
.
Poznámka
Prepínač -c
umožňuje preložiť program bez spojenia
(zlinkovania) zdrojových súborov. Výstupom takéhoto prekladu je
objektový súbor. To znamená, že pri vytváraní objektového súboru nie je
potrebné pridávať zoznam knižníc pre linkovanie.
Úloha
Vytvorte v súbore Makefile
pravidlá pre zostavenie
objektových súborov main.o
, playfair.o
a
bmp.o
.
Ako bolo uvedené, pre vytvorenie objektového súboru použijeme pri
preklade prepínač -c
a nebudeme linkvoať žiadnu knižnicu.
Pravidlo pre zostavenie modulu bmp.o
bude vyzerať
takto:
bmp.o: gcc -std=c11 -Wall -Werror -c bmp.c -o bmp.o
Podobným spôsobom zostavte zostávajúce pravidlá.
Poznámka
Pri vytváraní objektových súborov si dajte pozor, aby ste ich
zbytočne nepridávali do vášho Git projektu. Aby ste sa tomu
vyhli, odporúčame vám vytvoriť si samostatný súbor
.gitignore
v koreňovom priečinku vášho projektu (to je ten
priečinok, v ktorom sa nachádza priečinok .git/
). Do tohto
súboru je následne možné pridať zoznam súborov a priečinkov, ktoré sa do
Git repozitára nedostanú. V našom prípade môže tento súbor
vyzerať takto:
# content of .gitignore
*.o
Úloha
Overte funkčnosť vytvorených pravidiel.
Ak ste postupovali správne, po spustení akéhokoľvek pravidla príkazom
make
dôjde k vytvorenie príslušného objektového súboru.
Úloha
Upravte a overte cieľ ps1
tak, aby do spustiteľného
súboru spojil (zlinkoval) aj vzniknuté objektové súbory.
Pri zostavovaní spustiteľného súboru sú zatiaľ vstupom pre preklad všetky zdrojové súbory. Teraz ich nahradíme vytvorenými objektovými súbormi.
Úloha
Vytvorte samostatné pravidlo pre zostavenie cieľa all
,
ktoré bude závisieť len na existencii výstupného spustiteľného
súboru.
Pravidlo all
zrejme nájdete vo vačšine projektov, ktoré
pre zostavenie výsledného programu používajú nástroj make
a
súbor Makefile
. Toto pravidlo vykoná všetko potrebné pre
zostavenie požadovaného výstupu.
Toto pravidlo bude jednoduché - bude obsahovať len názov cieľa a
zoznam závislostí, ktorý bude tvorený len spustiteľným súboro
ps1
. Toto pravidlo nebude maž žiaden príkaz.
Úloha
Overte funkčnosť vytvoreného pravidla all
.
Ak ste postupovali správne, k prekladu by malo dôjsť iba vtedy, ak
došlo k zmene v niektorom zo súborov uvedených v závislostiach.
Aktualizovať ktorúkoľvek z nich môžete jednoducho spustením príkazu
touch
.
Premenné v súbore
Makefile
Vytvorený súbor Makefile
aktuálne obsahuje množstvo
opakujúcich sa častí. Napríklad sa neustále opakuje rovnaký zoznam
volieb prekladača gcc
. V súbore Makefile
môžeme tieto zápisy zjednodušiť použitím vlastných premenných. Tým
zjednodušíme prípadnú aktualizáciu týchto volieb v budúcnosti. Alebo aj
prípadnú výmenu prekladača gcc
za iný.
Úloha
Pomocou premenných nahraďte v príkazoch realizujúcich preklad meno prekladača, zoznam jeho prepínačov a zoznam potrebných knižníc.
Vytvorenie premennej v súbore Makefile
vyzerá podobne
ako v jazyku C, akurát nie je potrebné uvádzať jej typ:
VARIABLE=VALUE
Na mieste, kde budete chcieť hodnotu premennej použiť, zapíšete túto premennú v tvare:
$(VARIABLE)
V súbore Makefile
vytvorte tieto premenné:
CC
- názov, resp. absolútna cesta k prekladačuCFLAGS
- zoznam prepínačov pre prekladačLDLIBS
- zoznam knižníc potrebných pre prekladOUTPUT
- názov výsledného spustiteľného súboru
Poznámka
V súbore Makefile
je možné rovnako používať aj
komentáre. Makefile
podporuje len jednoriadkové komentáre.
Časť riadku, ktorá sa nachádza za znakom '#'
bude
ignorovaná. V prípade, ak sa znak '#'
nachádza na začiatku
riadku, bude ignorovaný celý riadok.
Úloha
Overte funkčnosť upraveného súboru Makefile
.
Ak ste postupovali správne, použitie súboru Makefile
je
bez zmien.
Úloha
V súbore Makefile
použite automatické premenné, ktoré
nástroj make
pozná.
Okrem používateľom definovaných premenných je možné v súbore
Makefile
použiť aj tzv. automatické premenné. Ich
kompletný zoznam nájdete v dokumentácii
k nástroju make
.
Pre potreby tejto úlohy si vystačíme s týmito automatickými premennými:
$@
- názov vytváraného cieľa daného pravidla$^
- zoznam všetkých závislostí (vstupných súborov)
Použitím uvedených premenných je možné upraviť napr. pravidlo pre
zostavenie objektového súboru playfair.o
takto:
playfair.o: playfair.c
$(CC) $(CFLAGS) -c $^ -o $@
Poznámka
V prípade, že má pravidlo len jeden vstupný súbor, na prístup k nemu
sa používa premenná $<
. Najčastejšie sa používa v tzv.
implicitných pravidlách alebo v pravidlách, kde je len
jeden vstupný súbor.
Úloha
Overte funkčnosť aktualizovaných pravidiel.
Pokiaľ ste postupovali správne, funkcionalita zostane nezmenená.
Implicitné pravidlá
Aktuálny zoznam pravidiel obsahuje tri pravidlá na zostavenie troch rôznych modulov. Jednotlivé pravidlá sa však od seba odlišujú len názvami vstupných a výstupných súborov. Ak by sme takýmto spôsobom mali organizovať projekt s 1000+ súbormi, nebolo by to veľmi praktické.
V súbore Makefile
je však možné vytvoriť aj tzv.
implicitné pravidlá. Tieto pravidlá dokážu automaticky
spracovať určité typy súborov bez toho, aby sme museli pre ne písať
vlastné pravidlá. My si takéto implicitné pravidlo vytvoríme pre
zostavenie objektových súborov.
Úloha
Vytvorte implicitné pravidlo pre zostavenie objektových súborov zo všetkých zdrojových súborov.
Pri tvorbe implicitných pravidiel sa používa zástupný znak
(z angl. wildcard) %
, ktorý nahrádza názov súboru
bez prípony. Implicitné pravidlo pre zostavenie objektových
súborov zo zodpovedajúcich zdrojových súborov bude vyzerať
nasledovne:
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
Úloha
Overte funkčnosť vytvoreného pravidla.
Aby bolo možné implicitné pravidlo overiť, ostatné pravidlá,
ktoré doteraz slúžili na vytváranie objektových súborov buď
zakomentujte alebo rovno zmažte. Funkcionalita samotného súboru
Makefile
zostane nezmenená, ale jeho obsah sa výrazne
zmenší.
Extending Tasks with Additional Commands
Vytvorené pravidlá v súbore Makefile
aktuálne obsahujú
len jeden príkaz na preklad a vytvorenie objektového súboru. V tomto
kroku do procesu zostavovania výsledného projektu pridáme nástroj
cppcheck
, ktorý zabezpečí statickú analýzu kódu, a nástroj
uncrustify
, ktorý zabezpečí preformátovanie kódu.
Výhodou bude, že ak niektorý zo zdrojových súborov neprejde statickou analýzou kódu, preklad a zostavovanie programu sa zastaví.
Úloha
Každé pravidlo začnite výpisom informácie o spúšťanom pravidle.
Každé pravidlo môže byť tvorené zoznamom príkazov spustiteľných v
príkazovom riadku. Ak chceme v príkazovom riadku vypísať reťazec na
obrazovku, použijeme na to príkaz echo
v tvare:
$ echo "Hello world"
Implicitné pravidlo pre zostavenie objektových súborov bude vyzerať takto:
%.o: %.c
"Building object file $@"
echo $(CC) $(CFLAGS) -c $< -o $@
Úloha
Potlačte výpis príkazov, ktoré sa majú vykonať.
Predtým, ako sa ľubovoľný príkaz v zozname príkazov pravidla vykoná,
ho príkaz make
vypíše na obrazovku. To však môže pôsobiť
veľmi rušivo a v prípade príkazu echo
aj zbytočne.
Ak chceme potlačiť vypísanie príkazu na obrazovku, tak pred príkaz
pridáme znak '@'
. Implicitné pravidlo pre
zostavovanie objektových súborov bude vyzerať takto:
%.o: %.c
@echo "Building object file $@"
@$(CC) $(CFLAGS) -c $< -o $@
Úloha
Zabezpečte, aby sa pred spustením prekladu vykonala statická analýza
kódu pomocou nástroja cppcheck
.
Ako bolo uvedené vyššie, v jednom pravidle sa môže nachádzať aj viac príkazov ako len jeden. Ak sa tak stane, tak v prípade, že dôjde pri vykonávaní niektorého z nich ku chybe (návratový kód príkazu bude iný ako 0), žiadne ďalšie príkazy sa nebudú vykonávať. Túto vlastnosť je možné s výhodou využiť v procese vytvorenia spustiteľného programu, kedy pred samotným prekladom spustíme statickú analýzu kódu. V prípade, že táto analýza zlyhá, proces vytvárenia programu sa okamžite skončí.
Nástroj cppcheck
slúži
na statickú analýzu kódu. Pri kontrole zadaní sa cppcheck
bude používať nasledovne:
$ cppcheck --enable=performance,unusedFunction --error-exitcode=1 *.c
Miesto toho, aby sme však tento príkaz zakaždým písali so všetkými
voľbami, ho budeme používať pomocou premennej CPPCHECK
. Tým
vieme jednoducho upravovať zoznam jeho volieb podľa potrieb:
CPPCHECK = cppcheck --enable=performance --error-exitcode=1
Implicitné pravidlo pre zostavovanie objektových súborov bude so statickou analýzou kódu vyzerať takto:
%.o: %.c
@echo "Building object file $@"
@$(CPPCHECK) $<
@$(CC) $(CFLAGS) -c $< -o $@
Úloha
Zabezpečte, aby sa pred spustením prekladu vykonalo formátovanie kódu.
Ak pracujete s veľkými vývojovými prostrediami, obyčajne obsahujú aj možnosť preformátovať kód. Túto operáciu je možné vykonať automaticky (napr. pri ukladaní súboru) alebo ručne.
Aj napriek tomu, že editor Vim vie tiež formátovať kód,
formátovanie kódu spustíme pri vykonávaní príkazov konkrétneho pravidla
v súbore Makefile
. Na formátovanie budeme používať nástroj
uncrustify
.
Jeho ukážkovú konfiguráciu nájdete v súbore uncrustify.cfg
.
Opäť si môžeme najprv vytvoriť samostatnú premennú so všetkými voľbami pre formátovanie kódu:
FORMAT_CODE = uncrustify --no-backup --replace -q
Implicitné pravidlo pre zostavovanie objektových súborov bude s formátovaním kódu vyzerať takto:
%.o: %.c
@echo "Building object file $@"
@$(FORMAT_CODE) $<
@$(CPPCHECK) $<
@$(CC) $(CFLAGS) -c $< -o $@
Ďalšie pravidlá a tipy
Na záver si ukážeme ešte niekoľko tipov, ako je možné s nástrojom
make
a so súborom Makefile
pracovať
efektívnejšie.
Úloha
Vytvorte pravidlo s názvom cieľa clean
, pomocou ktorého
zmažete z disku spustiteľný súbor ps1
a všetky ostatné
objektové súbory .o
.
Poznámka
Pri zostavovaní pravidla nezabudnite s výhodou použiť premennú
OUTPUT
.
Upozornenie
Dajte si pozor, aby ste nesprávnym zápisom tohto cieľa nezmazali všetky súbory nachádzajúce sa vo vašom projekte!
Pravidlo môže vyzerať napríklad takto:
clean:
@echo "Cleanup"
$(OUTPUT) *.o rm -rf
Úloha
Overte funkčnosť vytvorených pravidiel.
Ak ste postupovali správne, po spustení príkazu make
s
cieľom clean
dôjde k odstráneniu spustiteľného súboru
ps1
z disku vrátane všetkých objektových súborov.
Úloha
Overte možnosť spájania existujúcich pravidiel pri jednom spustení
príkazu make
.
Pri spúšťaní príkazu make
z príkazového riadku môžeme
naraz vypísať viacero názvov pravidiel bez toho, aby sme ich nutne
museli spúšťať ako samostatné príkazy. Napríklad príkazom
$ make clean all
dôjde najprv k spusteniu pravidla clean
a následne k
spusteniu pravidla all
.
Ďalšie zdroje
Znenie zadania č. 1 - Top Secret
GNU Make - dokumentácia k nástroju
make
(a súboruMakefile
)Prednáška č. 5/2014 - časť prednášky je venovaná problematike modulárneho programovania
Beautiful Makefile help messages with awk - ako vytvoriť pravidlo
help
, ktoré vypíše pomocníkaGitHubGist pre pravidlo
help
- množstvo ďalších príkladov, ako spraviť pomocníka pravidlohelp
uncrustify
- formátovač kóducppcheck
- nástroj na statickú analýzu C/C++ kódu