Pisanje bolj obsežnih programov terja delitev programov na module oziroma ločene datoteke. Zato bo funkcija main( ) v eni datoteki, druge funkcije pa verjetno v kateri drugi.
Tvorimo lahko knjižnico funkcij, ki so lahko skupinsko pomnjene v takih
modulih. To omogoča uporabo pripravljenih modulov v različnih programih,
v katere jih dodamo v času samega prevajanja.
Bolje je, če centraliziramo take definicije v eni datoteki in souporabljamo to datoteko v drugih modulih. Taki datoteki pravimo zaglavna datoteka (header file) in ima podaljšek .h
Standardne zaglavne datoteke upoštevamo v našem programu na naslednji znan način:
#include <stdio.h>
Lahko pa napišemo tudi lastne zaglavne datoteke, ki jih v našem programu upoštevamo tako:
#include ``mojaDatoteka.h''
Primer
![]() |
V programu main.c imamo funkcijo main( ), ki kliče funkcijo
WriteMyString( ), ta pa se nahaja v modulu WriteMyString.c
Prototip te funkcije pa se nahaja v zaglavni datoteki Header.h |
main.c:
#include "header.h" #include <stdio.h> char *drugNiz = "Pozdravljeni"; main(){ WriteMyString(MOJ_NIZ); /* klic funkcije iz drugega modula */ }WriteMyString.c:
extern char *drugNiz; void WriteMyString(char *taNiz){ printf("%s\n", taNiz); printf("Globalna spremenljivka = %s\n", drugNiz); }header.h:
#define MOJ_NIZ "Dober dan" void WriteMyString();
Območje (scope) zunanje spremenljivke ali funkcije nastopi na mestu
njene definicije in velja do zaključka datoteke (modula), v katerem je
definirana.
main(){ .... } int a; float b[10] void f1(){ .... } char c; float f2(){ .... }Funkcija main( ) ne vidi spremenljivk a in b ter funkcij f1 ( ) in f2( ). Tudi zato moramo prototipe funkcij navesti pred kodo, ki te funkcije uporablja.
Če potrebujemo naslavljanje neke zunanje spremenljivke, še preden je
bila definirana ozirama, če je definirana v nekem drugem modulu, moramo
povedati, da je zunanja:
extern int aaa;
Tako imamo v prejšnjem primeru niz drugNiz, ki je deklariran v main.c in souporabljen v WriteMyString.c , kjer smo ga deklarirali kot zunanjo spremenljivko.
Opozorilo: prefiks extern le napove spremenljivko in je ne definira.S tem še ne zasedemo nič pomnilnika.
Imeti moramo le eno definicijo takih spremenljivk, imamo pa lahko poljubno število njihovih zunanjih navedb.
Primer z zunanjo navedbo polja
main.c: int polje[100]:
file.c: extern int polje[];
Programerji običajno začnejo snovanje programa tako, da razbijejo
problem na več sekcij. Vsako od teh sekcij lahko realiziramo z eno ali
več funkcijami. Vse funkcije iste sekcije pomnimo v isti datoteki.
Podobno velja, če implementiramo objekte kot podatkovne strukture. Vse funkcije, ki delajo s tako strukturo, pomnimo v isti datoteki. Kasnejša sprememba "objekta" terja spremembo le v njemu ustrezni datoteki.
Najbolje je,če za vsako od teh izvornih datotek (s podaljškom .c) napišemo
še ustrazno zaglavno datoteko (z enakim imenom, toda s podaljškom .h).
Zaglavna datoteka vsebuje definicije vseh funkcij, ki jih "njena" izvorna
datoteka uporablja. Druge datoteke,ki uporabljajo te funkcije morajo na
začetku imeti ustrezen stavek #include z navedbo datoteke
.h
Vzemimo problem vzdrževanja naslednje večje zbirke izvornih datotek :
main.c f1.c ......... fn.c
Običajno bi jih prevedli na naslednji način:
cc -o main main.c f1.c ......... fn.c
Če vemo, da nekaterih datotek nismo popravljali in ni potrebno ponovno prevajanje, zadošča le povezovanje njihove objektne kode:
cc -o main main.c f1.c ... fi.o .. fj.o ... fn.c
Uporaba orodja make omogoča, da nam za te podrobnosti ni potrebno skrbeti. "Sprogramirati moramo le zaporedje ukazov, ki opisuje, kako lahko naš program (ali sistem več programov) zgradimo iz naših izvornih datotek)
Pri tem moramo upoštevati pravila odvisnosti in pravila izgradnje.
Pravilo odvisnosti ima levo in desno stran, ločeni z dvopičjem :
levaStran : desnaStran
levaStran podaja imena ciljev (na primer imena programov ali datotek), ki jih želimo graditi. desnaStran navaja imena datotek, od katerih je cilj odvisen (na primer imena izvornih datotek, zaglavnih datotek, podatkovnih datotek) .
Sestaviti moramo datoteko "make", ki pomni tako zaporedje ukazov. Pogosto
damo tej datoteki ime
Makefile.
Orodje sprožimo tako
make (UNIX poskuša izvesti ukaze v datoteki Makefile)
oziroma tako
make -f mojmake (UNIX bo izvedel ukaze v datoteki mojmake)
Če je nek cilj starejši od datotek, ki nanj vplivajo, uporabi orodje make ustrezna pravila izgradnje (prevajanje, povezovanje, lahko tudi kaj drugega).
Opomba: datoteke "make" ubogajo tudi druge ukaze UNIX, ki jih običajno
tipkamo v ukaznih vrsticah. Zato jih lahko uporabimo tudi za tvorbo rezervnih
kopij (backup), poganjanje programov, čiščenje direktorijev ipd.
Primer take datoteke:
prog: prog.o f1.o f2.o c89 prog.o f1.o f2.o -lm etc. prog.o: header.h prog.c c89 -c prog.c f1.o: header.h f1.c c89 -c f1.cf2.o: --- ----
Kar lahko razumemo tako:
prog.o je odvisen od dveh datotek. Če je katera od teh spremenjena, je potrebno ponoviti prevajanje. Podobno velja za f1.o in f2.o.
Lahko pa imamo implicitna pravila, ki so bolj splošna.
Vzemimo na primer
f1.o: f1.c cc -c f1.c f2.o: f2.c cc -c f2.c
in to posplošimo v:
.c.o: cc -c $<
To lahko beremo kot ukaz "podaljšekIzvora" " podaljšekCilja"
$< je skrajšava za ime datoteke s podaljškom .c
V datoteki imamo lahko tudi komentarje, ki sledijo znaku #.
SOURCES = main.c f1.c f2.c CFLAGS = -g -C LIBS = -lm PROGRAM = main OBJECTS = (SOURCES: .c = .o)Makro uporabimo na naslednji način
$(macro_name)
Primer:
$(PROGRAM) : $(OBJECTS) $(LINK.C) -o $@ $(OBJECTS) $(LIBS)
Opomba:
$* | ime trenutne odvisne datoteke (brez suffiksa) |
$@ | polno ime trenutnega cilja |
$< | ciljna .c datoteka |
# # Makefile # SOURCES.c= main.c WriteMyString.c INCLUDES= CFLAGS= SLIBS= PROGRAM= main OBJECTS= $(SOURCES.c:.c=.o) .KEEP_STATE: debug := CFLAGS= -g all debug: $(PROGRAM) $(PROGRAM): $(INCLUDES) $(OBJECTS) $(LINK.c) -o $@ $(OBJECTS) $(SLIBS) clean: rm -f $(PROGRAM) $(OBJECTS)