Sunteți pe pagina 1din 28

CAPITOLUL 12-13.

Programarea sub sistemul de operare UNIX

Rezumat: dezvoltarea programelor sub UNIX : prezentarea modului de compilare a programelor :


actualizarea automată a componentelor unui program ; depistarea inconsistenţelor din programe C
(utilizarea make si lint).

8.1. Dezvoltarea programelor C sub UNIX

Sistemul de operare UNIX reprezintă un foarte bun suport de programe . Având în vedere faptul că
acest sistem de operare este în cea mai mare parte scris în limbajul C. UNIX oferă un mediu ideal pentru
programarea în acest limbaj . Unul dintre cele mai importante motive care susţin această afirmaţie este
acela că programatorul pentru a face programarea cât mai facilă, au dezvoltate
bibliotecile de funcţii .Multe dintre funcţiile de bibliotecă accesează servicii de bază ale sistemului de
operare prin intermediul funcţiilor sistemului, permiţând un acces mai apropiat de necesităţile tipice
programării. Alte funcţii de bibliotecă realizează prelucrări speciale ,cum ar fi funcţii matematice
prelucrări pe şiruri de caractere ,prelucrări grafice etc.
Acest curs işi propune prezentarea paşilor necesari compilării unui program C şi nu învaţarea
programări în limbajul C.
Compilatorul prezentat va fi compilatorul gcc pentru sistemul de operare ULTRIX

8.1.1.Etapele prelucrării unui program C

Pentru a scrie un program ,fie şi în limbajul C, trebuie utilizat un editor ,de exemplu ,vi. Pentru a face
cunoscut compilatorului că fişierul creat I se adresează ,la numele fişierului trebuie adăugată
extensia .c.
Crearea unui fişier conţinând cod sursa C, din punct de vedere al editorului este similară creării unui
oricărui alt fişier text. Deci scrierea unui fişier al cărui conţinut să respecte sintaxa şi structura unui
program C este strict responsabilitatea programatorului.
Să considerăm programul din exemplul următor :
/*Fişier :test.c
programul converteşte caracterele TAB din fişierul standard de intrare (stdin) în numărul corespunzător de
caractere
spaţiu obişnuite, şi scrie intrarea astfel transformata în
fişierul standard de ieşire (stdout)
*/
# include <stdio.h>
# define DIMTAB 8
/* calculează numărul de coloane până la următorul tab */
detstop (col)
int col ;
{
return (DIMTAB- (col %DIMTAB) );
}
main( )
{
int c; /* caracterul citit din stdin */
int poscol= 0 ; /*coloana în care este caracterul */
int inc ; /*numărul de coloane până la următorul tab */

wilw ( ( c= getchar ( ) ) !=EOF)


swictch ( c )
{
case ‘ \ t ‘ : /* tab * /
inc = detstop ( poscol ) ;
poscol += inc ;
for ( ; inc >0 ; inc - - )
putchar (‘ ‘);
break ;
case ‘\n ‘: /*linie nouă */
putchar ( c) ;
poscol = 0;
break;

default:
putchar ( c) ; /*orice altceva */
poscol + + ;
break ;
}
}
/* sfârşit */
Primele cinci linii sunt comentarii care prezintă efectul rulării programului asupra datelor de intrare .
Caracterele /* marchează începutul comentariului, iar caracterele */ marchează sfârşitul lui. Tot ceea ce
există între acest marcaj, este ignorat de compilator. Următoarele linii sunt directive de procesare , adică
instrucţiuni adresate preprocesorului de C . In prima fază de compilare , preprocesorului prelucrează
codul sursă conform directivelor primite . De exemplu , se poate utiliza directiva #define pentru a
defini constante simbolice şi/sau macrodefiniţii . Constantele simbolice sunt identificatori care pot
fi utilizaţi în program în locul constantelor numerice . In exemplul nostru , DIMTAB este asociat
constantei numerice 8 . Prin definirea constantelor simbolice , programul devine mai uşor de întreţinut si
modificat .Macrodefiniţile sunt similare unor funcţii scurte . De exemplu , următoarea macrodefiniţie
determină daca un caracter este o cifră :
# define numeric ( n ) ( n>= ‘ 0 ‘ & & n <=’9’)
Atunci când sunt utilizate constante simbolice şi macrodefiniţii a devenit o practica standard colectarea lor
într-un fişier , numit fişier atent . Deşi compilatorul nu impune restricţii asupra numelui fişierelor antet ,
prin convenţie numele lor conţin sufixul .h
(header files ). Numele fişierelor antet trebuie să apară într-o directiva # include în fiecare fişier sursă în
care sunt utilizate respectivele constante simbolice şi/sau macrodefiniţii . În exemplu anterior au fost
folosite getchar şi putchar care sunt macrodefiniţii definite în studio.h . Restul liniilor din fişier
reprezintă codificarea în limbajul sursa C , a algoritmului de rezolvare a problemei .
pentru a compila fişierul test.c trebuie dată comanda :
$ gcc test . c
Utilitarul ce apelează succesiv preprocesorul , compilatorul si editorul de legături.
Preprocesorul expandează constantele simbolice si macrodefiniţiile , si include fişierele antet. Compilatorul
generează câte un fişier obiect pentru fiecare fişier sursă prelucrat . Editorul de legături caută în
bibliotecile specificate funcţiile utilizate în program si combină modulele obiect pentru acela funcţii cu
module obiect ale programului .Implicit editorul de legături caută bibliotecă standard libc.a . Dacă se
doreşte specificarea unor anumite biblioteci , aceasta se poate face utilizând opţiunea – l urmată de
numele bibliotecii .
De exemplu :
$ gcc test.c –1m
Editorul de legături va căuta biblioteca matematică libm.a . Aşa cum se observă din exemplu , utilizează
abreviaţii pentru numele bibliotecilor , respectiv m reprezintă biblioteca numită libm.a . Spre deosebire
de majoritatea opţiunilor utilitarelor sistemului de operare UNIX . opţiunea –l este valabilă pentru
fişierele care o preced pe linia de comanda .
Implicit , bibliotecile sunt căutate în cataloagelor / lib , /usr/lib , /usr/local/lib în aceasta ordine . Dacă se
doreşte specificarea unor cataloage în care sa fie mai întâi căutate anumite biblioteci , se poate utiliza
opţiunea –l , urmată de numele catalogului . De exemplu :
$ gcc test . c –L/usr/local/A-lob
determină căutarea bibliotecii libob.a mai întâi în catalogul /usr/local/A, apoi în cataloagele implicite .
De asemenea , se poate inhiba căutarea bibliotecilor în cataloagele standard , utilizând opţiunea – L.
Comanda:
$ gcc test . c –L _ /usr/local/A-lob
va determina căutarea bibliotecii doar în catalorul / ust/local/A
Ultimul pas constă în crearea fişierului executabil , numit implicit a. out .Specificarea explicită a unui
nume pentru fişierul executabil se poate face utilizând operaţiunea –0 urmată de numele fişierului :
$ gcc –0 test test . c
Dacă se doreşte suprimarea etapei de editare de legături , deci doar obţinerea imaginii obiect , se poate
utiliza opţiunea –c .
$ gcc –c text . c
va avea ca efect obţinerea fişierului obiect test .o .
compilarea programelor constituie din multe fişiere sursa si antet , care depind unele de altele într-un mod
complex , poate deveni o activitate dificilă şi consumatoare de timp datorită în principal dificultăţi de a
determina ce module trebuie recompilate ca efect al modificărilor din modulele de care acestea depind .
Utilitarul make automatizează acest proces . El este prezentat în secţiunea 9.2

8.1.2 . Utilizarea compilatorului

Principalele funcţii ale compilatorului de C sunt următoarele :


verifică corectitudinea codului sursă C
generează instrucţiuni în limbaj maşina pentru codul sursa C
grupează instrucţiunile într-un modul obiect care poate fi prelucrat de editorul de
legături .

Comanda cc

Pentru a lansa compilatorul , trebuie dată comanda cc a cărei sintaxa este următoarea :
cc – opţiuni . . . nume – fişier. . .
numele fişierelor şi ale opţiunilor sunt separate prin spaţii . Compilatorul creează un fişier obiect pentru
fiecare fişier sursa, iar editorul de legături construieşte fişierul executabil din fişierele obiect . Dacă în linia
de comanda este specificat doar un fişier sursă , atunci fişierul obiect este şters după ce fişierul executabil
a fost creat . O eroare de compilare într-un fişier sursă nu împiedica compilarea celorlalte .
opţiunile de pe linia de comanda se aplica tuturor fişierelor . Opţiunile care nu sunt recunoscute de
compilator sunt transmise editorului de legături .
Dacă există opţiuni conflictuale pe linia de comandă numele implicit al fişierului executabil este a.out ,
dar aşa cum am văzut , el poare fi modificat .

Prezentarea celor mai frecvent utilizare opţiuni


Tabelul 8.1
Opţiune Efect

-c suprimă editarea de legături , se creează doar fişiere obiect


-0 nume fişierul executabil se va numi nume si nu a.out
-w suprimă avertismentele si mesajele de informare trimise de compilator
-std generează avertismente pentru un cod care nu este standard ANSI C
-g creează tabela de simboli pentru depanarea simbolica
-00 nu optimizează codul generat
-02 se optimizează codul generat
rulează doar procesorul , iar rezultatul este trimis către ieşirea standard , stdout
ocpp nu este rulat procesorul ; această opţiune nu are efect dacă este `specificata una dintre opţiunile -E sau -
P
lista caută fişierele antet in cataloagele specificate în lista de cataloage
-I inhibă căutarea fişierelor antet în catalogul standard (usr / include )

Interpretarea mesajelor compilatorului


Dacă compilatorul detectează erori în fişierul sursa , el trimite mesaje de eroare către fişierul standard de
eroare stderr, care , dacă comanda cc este interactiv si nu a fost redirectat , este chiar terminalul
Mesajele compilatorului au următoarea formă :
Nume_fişier :nr_linie : severitate : mesaj
Severitatea mesajului specifica gravitatea erorii . ea poate fi :
informativa - pentru a acţiune beningă
avertisment - pentru un cod care nu este incorect , dar poate fi îmbunătăţit printr-o
cercetare mai atenta (de exemplu , o secvenţa escape nedefinita
eroare -pentru un cod incorect : compilarea continuă , dar nu este creat fişier
obiect
eroare fatala – pentru un cod a cărui incorectitudine nu poate fi tolerata ;
compilarea se opreşte si bine înţeles nu este creat fişierul obiect
Mesajele compilatorului sunt de fapt diagnostice . Ele trebuie sa ofere suficienta informaţie , astfel încât
sa poată fi determinata atât cauza probabilă a erorii , cât şi locul în care s-a produs .
8.1.3Editorul de legături

Editorul de legături este invocat automat atunci când este lansată comanda cc , cu excepţia cazului când se
specifică opţiunea –c . Editorul de legături poare fi şi separat , utilizând comanda id . In oricare dintre
cazuri , mai multe fişiere obiect sunt combinate într-un fişier executabil .
Opţiuni
Tabelul 8.2
Opţiune Efect
-O nume_fişier fişierul executabil se numeşte nume_fişier
-1nume caută biblioteca libnume.a pentru a găsi referinţele ne rezolvate
g caută biblioteci în catalorul catalog, înainte de a căuta în cataloagele implicite: /lib, /usr /local /lib
nu caută biblioteci în cataloarele implicite
suprimă mesajele care nu reprezintă erori fatale
-V tipăreşte numele fiecărui fişier, pe măsura Ce Este prelucrat
l tipăreşte numele fişierelor care conţin simbol , şi de asemenea informaţii despre tipul simbolului , daca
acesta este definit şi/sau referit în fişierul respectiv

Interpretarea mesajelor editorului de legături

Mesajele editorului de legături sunt trimise ca, şi în cazul compilatorului , către fişierului sandard stderr .
Mesajele au următoarea formă:
Id : mesaj
Cele mai frecvente erori se referă la următoarele situaţii :
dacă modulele ce trebuie legate definesc mai multe funcţii main ( ) , editorul de
legături trimite un mesaj de avertizare şi încearcă şa creeze un fişier executabil ce primul main ( ) găsit ;

dacă referinţa la un simbol nu poate fi rezolvată , este trimis un mesaj de eroare şi nu este creat fişierul
executabil ;
dacă nu poate găsi o bibliotecă specificată în linia de comandă , editorul de legături se
opreşte şi nu creează fişierul executabil ;
dacă un simbol extern este definit de mai multe ori , este trimis un mesaj de eroare , şi
nu se creează fişierul executabil .
8.1.4. Crearea bibliotecilor obiect
Sistemul de operare oferă multe biblioteci obiect , care conţin cele mai frecvent utilizare funcţii .Dar
programatorul poate crea şi propriile sale biblioteci , utilizabile în acelaşi mod cu cele de sistem .
Pentru a crea o bibliotecă , trebuie mai întâi create modulele obiect . de exemplu :
$ cc – c f1.c f2.c
Apoi trebuie creată biblioteca propriu-zisă utilizând comanda ar cu opţiunea –r şi plasate modulele obiect
în ea
$ ar –r obibliotecă f1. 0 f2 .0
un sfârşit trebuie lansată comanda ranlib , care adaugă tabela obiectelor conţinute în bibliotecă , pentru a
facilita sarcina editorului de legături .
$ ranlib obibliotecă
Biblioteca obibliotecă poate acum fi specificată într-o comandă cc sau ld , la fel ca orce altă bibliotecă .
8.1.5. Depanarea programelor

Sistemul de operare UNIX pune la dispoziţie mai multe utilitare care să faciliteze procesul de depanare a
programelor . Unul dintre cele mai cele mai utilizate este lint. Lint verifică programele din punct de
vedere al unor potenţiale erori şi al unor probleme legate de probabilitate . Spre de deosebire de
compilatorul de C, lint este foarte strict. El detectează şi raportează o mare varietate de probleme şi
potenţiale probleme . Deşi. Desigur programatorul este liber să ignore avertismentele lint-ului , acesta
evidenţiază în general o eroare , o construcţie neportabilă sau o violare a standardului unei bune
programări . Utilitarul lint nu reprezintă numai un ajutor în depanarea programelor , dar şi o modalitate de
îmbunătăţire a stilului de programare .
Utilitarul lint este prezentat în secţiunea 8.3
Pentru problemele care scapă atât compilatorului, cât şi lint-ului , se poate utiliza depanatorul simbolic
sdb. Utilitarul sdb permite analiza execuţiei programului . Programul poate fi executat pas cu pas ,
vizualizând starea variabilelor , a parametrilor care definesc mediul de execuţie etc. Pentru a putea utiliza
depanatorul simbolic , la compilarea programului trebuie utilizată opţiunea –g. Referitor la utilizarea
depanatorului simbolic , este bine sa fie consultată documentaţia proprie sistemului de operare utilizat
efectiv.

8.2.Make
8.2.1. Prezentare generală
Tendinţa manifestată în prezent , de proiectare modulară a programelor conduce , inevitabil , la necesitatea
manipulării unui număr mare de fişiere în cadru unei aplicaţii.
Sistemul de operare UNIX pune la dispoziţia programatorilor utilitarului make, care asigură menţinerea
actualizată a programelor constituite dintr-un număr de fişiere generate în diferite moduri . Scopul
principal al utilitarului make este de a determina automat ce fişiere , reprezentând module ale unui
program, trebuie recompilate , şi de a trimite comenzile necesare . In acest mod , programatorul poate
ignora :
dependenţe dintre fişiere
efectul modificării unor fişiere asupra celorlalte
secvenţa exactă de operaţii necesare pentru genera o noua ă versiune a programului .
Pentru a putea fi folosit , make are nevoie de un fişier de descriere a comenzilor prin care sunt create
fişierele ce formează programul şi a relaţiilor dintre acestea . Ori de câte ori este făcută o modificare într-
unul dintre fişiere, lansarea utilitarului make va reconstrui programul , recompilând doar modulele afectate
direct sau indirect de modificarea făcută .
In continuare va fi prezentat utilitarul make , în particular implementarea GNU. Exemplele ce vor fi
prezentate se vor referi la programe C ,dar make poate fi folosit pentru orice limbaj de programare al cărui
compilator poate fi lansat printr-o comandă din interpretorul de comenzi al sistemului .
Aşa cum a fost deja menţionat , pentru a folosi utilitarului make trebuie creat un fişier de descriere numit ,
în general makefile sau Makefile .
Fişierul de descriere este compus din reguli . O regulă specifică cum şi când trebuie creat sau actualizat un
anumit fişier , numit destinaţia regulii. Specificarea este realizată prin scrierea fişierelor de care destinaţia
depinde şi a comenzilor ce trebuie executate pentru a crea sau actualiza destinaţia .
Să presupunem că programul a cărui imagine executabilă dorim să obţinem în fişierul prog,este constituit
din şase fişiere sursă C : fs1.c , fs2.c ,…, fs6.c şi din fişierele atent inc.1.h,inc. 2.h, inc3.h. De asemenea
toate fişierele sursă include fişierul inch1.h, dar numai fişierul fs2.c îl include pe inc2.h şi numai fişierul
fs3.c îl include pe inc3.h. Un mod direct şi simplu (şi în acelaşi timp simplist) de a descrie aceste
dependenţe şi a specifica cum să fie compilate şi legate aceste fişiere este prin scrierea următorului fişier
makefile.
Prog :fs1.o fs2.o fs3 . o fs4.o \
fs5.o fs6.o
cc – o prog fs1.o fs2.o fs3 . o fs4.o \
fs5.o fs6.o

fs1.o : fs1.c inc1.h


cc – c fs1 .c
fs2.o : fs .c inc1. h inc1. h
cc – c fs2 . c

fs3.0 : fs 3 . c inc l. h inc3 . h


cc - c fs 3 . c
fs4.0 : fs4 . c incl . h
cc – c fs 4 . c
fs5.0 : fs5 . c incl . h
cc – c fs5 . c
fs6.0 : fs 6 . c incl . h
cc – c fs 6 . c
Fiecare linie lungã a fost despărţită în douã linii, folosind caracterul de continuare ’\’ ,
pentru a fi mai uşor de citit.
Fiecare fişier generat din mai multe fişiere sursă reprezintă destinaţia unei reguli. În cazul exemplului
nostru, fişierele obiect (fsl.o,...,fs6.o), precum şi fişierul executabil prog sunt astfel de destinaţii. Din punct
de vedere sintactic, destinaţia apare la începutul liniei, urmatã de douã puncte:.
După cele douã puncte urmează lista de fişiere ce reprezintă dependenţele fişierului destinaţie, adică toate
fişierele care trebuie luate în considerare atunci când destinaţia este actualizată. În exemplul nostru, prog
depinde de toate fişierele obiect. Fişierele obiect depind, la rândul lor, de fişierele sursã C şi de anumite
fişiere antet, aşa cum rezultã din descriere.
În mod implicit, make începe prin a lua în considerare prima regulã, motiv pentru care prima regulã va fi
cea referitoare la construcţia fişierului executabil (sã-l numim destinaţie principalã). Celelalte reguli vor fi
prelucrate pentru cã destinaţiile lor reprezintă dependenţe pentru destinaţia principalã.
După fiecare linie de descriere a destinaţiei şi dependenţelor, sunt scrise comenzile care specificã cum
trebuie actualizatã destinaţia. Liniile corespunzătoare comenzilor trebuie sã înceapă cu un caracter TAB
pentru ca make sã le poată deosebi de celelalte linii. Utilitarul make nu face decât sã lanseze în execuţie
comenzile specificate, atunci când destinaţia trebuie actualizatã. Deci scrierea corectã a comenzilor este,
strict, răspunderea programatorului.

Cum funcţionează make

Dacã fişierul de descriere se numeşte makefile sau Makefile, atunci prin simpla lansare a comenzii make,
utilitarul make va începe prelucrarea fişierului de descriere.
El va încerca sã prelucreze prima regulã, cea referitoare la construcţia fişierului prog din modulele obiect.
Dar, pentru aceasta, trebuie sã prelucreze regulile pentru toate fişierele de care prog depinde, adică toate
fişierele obiect.
Fiecare dintre aceste fişiere este prelucrat în conformitate cu propria sa regulã, care specificã modul în care
fişierul obiect va fi creat sau actualizat prin compilare din fişierul sursã.
Compilarea va avea loc dacã una dintre dependenţele sale (fişierul sursã sau antet) este mai recentã, sau
dacã fişierul obiect nu existã.
Înainte de a lansa comanda de compilare, make încearcă sã actualizeze fişierele sursã şi antet. În exemplul
nostru, în fişierul de descriere nu existã nici o regulã pentru ele, deci make nu va face nimic.

Utilizare variabilelor poate simplifica scrierea fişierului de descriere

În exemplul nostru, a fost necesar sã scriem lista fişierelor obiect de douã ori:
prog: fs1.o fs2.o fs3.o fs4.o fs5.o fs.6 .o
cc – o prog fs 1.o fs2. o fs 3. o fs 4. o fs 5 .o fs6 .o

O astfel de duplicare poate conduce la erori. De exemplu, atunci când este adăugat un nou fişier, el este
introdus în lista dependenţelor şi poate fi uitat în comanda de editare legături. O astfel de situaţie poate fi
evitatã prin utilizarea variabilelor.

Definirea unei variabile specificã o valoare de tip şir de caractere pentru variabila respectivã. Substituirea
valorii `variabilei cu şirul de caractere pe care îl reprezintă, se face respectând sintaxa:

$ (nume _variabilã)

În scrierea fişierelor de descriere, a devenit o practicã standard utilizarea unei variabile cu numele objects,
OBJECTS, objs, obj, OBJ care este asociatã listei fişierelor obiect
În cazul exemplului nostru putem defini:

objects = fs1.o fs2.o fs3.o fs4..o \


fs5.o fs6. o
Regula va deveni:

prog : $ (objects)
cc – o prog $ (objects)

Reguli implicite

Utilitarul make are reguli implicite de actualizare a fişierelor obiect, utilizând comanda cc –c. Deci dacã se
doreşte folosirea acestei comenzi de compilare, se poate omite scrierea comenzii de compilare în regula
corespunzătoare fişierului obiect. În acest caz fişierul sursã C va fi luat automat în considerare şi adăugat în
lista de dependenţe a fişierului obiect. Consecinţa directã a acestui fapt este cã specificarea fişierului sursã
poate fi omisã din lista dependenţelor fişierului obiect corespunzător.

Exemplul nostru devine:

objects = fs1.o fs2.o fs3.o fs4..o \


fs5.o fs6. o
prog : $ (objects)
cc – o prog $ (objects)

fs1. o : incl .h
fs2. o : incl .h inc2 .h
fs3. o : incl .h inc3 . h
fs4. o : incl .h
fs5. o : incl .h
fs6. o : incl .h

În exemplul anterior , regulile corespunzătoare fişierelor obiect specificã numai relaţii, nu comenzi.
Aceasta ne sugerează o rescriere a lor după criteriul dependenţelor şi nu al destinaţiei.

Exemplul nostru devine:

objects = fs1.o fs2.o fs3.o fs4..o \


fs5.o fs6. o

prog : $ (objects)
cc – o prog $ (objects)
$ (objects) : incl .h
fs2. o : inc2 .h
fs3. o : inc3 .h

Fişierul inc1.h apare ca dependenţã pentru toate fişierele obiect. Fişierele inc2.h şi inch3.h apar ca
dependenţe doar pentru fişierele fs2.c, respectiv fs3.c, aşa cum a fost specificat.
Regulile implicite reprezintă, în general, modul practic de scriere a fişierelor de descriere.

Alt fel de reguli

Fişierele de descriere pot conţine şi alt tip de reguli decât cele referitoare la compilarea unui program. De
exemplu, regula de ştergere a fişierelor obiect după ce fişierul executabil a fost creat, pentru a nu ocupa
inutil spaţiu pe disc. Aceastã regulã poate fi scrisã în felul următor:

clean:
rm $ (objects)

Pentru ca regula de ştergere sã fie luatã în considerare de make, apelul utilitarului trebuie sã aibă
urmãtoarea formã:

$ make clean

Pentru a ignora aceastã regulã, este suficient sã fie scris apelul standard:

$ make
Existã o observaţie importantã. Regula de ştergere a fişierelor obiect trebui sã fie scrisã la sfârşitul
fişierului de descriere, nu la începutul lui, pentru a nu deveni destinaţia principalã.
Scopul fişierului de descriere este şi rãmâne acela de a crea fişierul executabil prog.

8.2.2. Fişierele de descriere

Aşa cum am vãzut deja, informaţia de care are nevoie utilitarul make este memoratã în fişierul de
descriere.

Numele fişierului de descriere

Atunci când este lansatã comanda make <CR>, utilitarul make încearcã sã gãseascã un fişier numit
makefile sau Makefile, în aceastã ordine. Deci, în mod normal, fişierul de descriere ar trebui sã aibã unul
dintre aceste douã nume (este recomandabil sã se numeascã Makefile, pentru cã fiind scris cu majusculã va
apare în lista de fişiere la începutul catalogului, fiind mai uşor de localizat).
Dacã se doreşte folosirea unui alt nume pentru fişierul de descriere, acesta poate fi specificat în comanda
make utilizând opţiunea –f
De exemplu:

$ make -f numefisdescriere
Ce conţin fişierele de descriere

Fişierele de descriere conţin patru tipuri de elemente:


- Definiţii de variabile;
- Reguli;
- Directive;
- Comentarii
În continuare va fi fãcutã o prezentare mai detaliatã a acestor fişiere.

8.2.3. Definirea variabilelor

O variabilã este caracterizatã prin nume şi valoare. Definirea unei variabile se face prin asocierea unui şir
de caractere identificatorului. Valoarea variabilei este şirul de caractere asociat.
Numele unei variabile poate fi orice secvenţã de caractere, cu excepţia : , #, = şi a spaţiilor. Este
recomandabil ca numele sã conţinã numai litere, cifre, şi/sau caracterul _ pentru a putea fi transportate.
Deşi a intrat în tradiţie utilizarea majusculelor pentru numele variabilelor, atunci, atunci când sunt interne
fişierelor de descriere, este recomandabilã scrierea lor cu litere mici.
Pentru a substitui valoarea unei variabile, numele variabilei trebuie scris între paranteze rotunde, sau
acolade, precedat de semnul $.De exemplu:

$ (var)
sau
$(var)
reprezintă referiri ale variabilei var
Având în vedere semnificaţia deosebită a semnului $ pentru ca numele unei variabile să poată include acest
caracter , el trebuie să apară duplicat în numele variabilei .
Referirea variabilelor poate fi făcută în orice context : dependenţe , destinaţii , comenzi , directive valori
pentru alte variabile .
Substituirea valorii unei variabile este strict o substituţie textuală.
De exemplu :
id = c
prog .o :prog . c
$(id)$(id) prog.c
poate fi utilizat pentru a compila fişierul prog.c .Spaţiile din jurul şirului de caractere atribuit variabilei
sunt ignorate la atribuire , deci valoarea variabilei id este caracterul c.
Numele unei variabile poate fi format doar dintr-un caracter , caz în care la referirea variabilei nu mai este
necesară utilizarea parantezelor sau a acoladelor . De exemplu : vom scrie $x şi nu $(x) sau ${x} . Aceasta
nu este ,însă , o tehnică .

Tipuri de variabile

Există două modalităţi de a atribui valori unei variabile . Ele diferă din punct de vedere al modului
de definire şi substituire a valorii variabilei. Prima categorie este reprezentată de variabilele care se
expandă recursiv. Din punct de vedere sintactic, acest tip de variabile sunt definite utilizând semnul =.
Dacă o astfel de variabilă conţine referinţe la alte variabile, aceste referinţe sunt expandate ori de câte ori
variabila respectivă este substituită.
De exemplu:
var1= $ (var2)
var2 = $ (var3)
var3 = oare?

Regula:
Dest: ; echo $ (var1)
Va tipări oare? Pentru că var1 sete expandată în var3, expandată la rândul ei în oare? Pentru mărirea
eficienţei se poate utiliza cea de a doua categorie de variabile. Din punct de vedere sintactic, aceste
variabile se definesc folosind semnul : =. Valoarea lor este determinată prin expandarea referinţelor chiar
la definirea lor.
De exemplu:
X:= va1_x_1
Y:= $ (x) va1_y_
X:= val_x_2
Este echivalent cu:
Y:= val_x_2

8.2.4. Scrierea regulilor

In general o regulă are una dintre următoarele forme:


Destinaţie... : dependenta ...
Comanda < CR>
.
.
.
sau

Destinaţie ... : dependenta ... ;comanda < CR >


Comanda < CR >
.
.
.

Semnificaţia notaţiei... fiind acea de repetiţie


Destinaţia este reprezentată de unul sau mai multe nume de fişiere separate prin spaţii. In general, există o
singură destinaţie pentru o regulă, dar sunt cazuri care justifică existenţa mai multor destinaţii pentru o
regulă .
Dependenţele unei (unor) destinaţii sunt reprezentate printr-o listă de fişiere ale căror nume sunt separate
prin spaţii .
Liniile de comandă încep cu caracterul TAB .există o singură excepţie referitoare la prima comandă .
Aceasta poate fi scrisă la fel ca celelalte comenzi sau în continuarea listei dependenţelor, caz în care
trebuie precedată de caracterul ;.

Utilizarea caracterelor speciale în numele fişierelor


Numele fişierelor port conţine carctere speciale *,? .,~ cu semnificaţia cunoscută din
interpretorul de comenzi Bourne .
Expandarea caracterelor speciale are loc automat în destinaţii , dependente şi în comenzi
În alte contexte , această expandare se va produce doar dacă este cerută explicit prin intermediul unor
funcţii speciale ,numite funcţii wildcard.
Expandarea caracterelor speciale poate fi inhibată ,prin precedarea lor de caracterul \.De exemplu , fis\*er
reprezintă numele unui fişier ce conţine caracterele : f, I ,s, *, e, r .
Expandarea caracterelor speciale nu are loc atunci când este definită o variabilă .De exemplu dacă definim
variabila objects in felul următor :

objects = *.o
atunci valoarea variabilei objects este şirul de caractere *.o . Dar dacă aceasta variabila va fi folosită într-
o comanda , expandarea va avea loc în acel moment .
Există şi capcane care pot apărea în utilizarea acestor caractere speciale .Să presupunem că exprimăm
faptul că fişierul executabil va fi format din toate fişierele obiect din catalogul curent şi scriem aceasta
astfel “:

objects = *.o
fexec : $(objects)
cc-o fexec $( objects)

Valoarea variabilei objects este şirul *.o. Expandarea caracterelor speciale are loc în regula
corespunzătoare lui fexec , astfel încât orice fişier obiect existent va intra în lista dependenţelor fişierului
fexec şi va fi recompilat dacă este necesar . Dar ce se întâmplă dacă se şterg toate fişierele obiect din
catalogul curent ? Caracterele *.o se for expanda în “nimic “ iar imaginea executabilă nu va fi creată .
Există desigur , o soluţie a acestei probleme . Ea este obţinută cu ajutorul unor funcţii speciale care vor fi
prezentate în continuare

Funcţii pentru expandarea caracterelor speciale

Expandarea caracterelor speciale are loc automat în reguli , dar nu se produce, în mod normal , la definirea
unei variabile sau în interiorul argumentelor unei funcţii . Dacă se doreşte expandarea şi în aceste din urmă
cazuri , trebuie folosită funcţia wildcard , a cărei sintaxă este : ( wildcard tipar ).
Dacă acest sir de caractere este întâlnit într-un fişier de descriere , el va fi înlocuit cu lista numerelor
fişierelor existente separate prin spaţii , care se potrivesc tiparului .
De exemplu dacă dorim lista tuturor fişierelor sursă C , funcţia wildcard va fi următoarea : ( wildcard
*.c) .
Revenind la problema anterioară , ea poate fi rezolvata prin scrierea următorului fişier de descriere :
Objects :=$(subst .c, .o, $(wildcard *.c)
fexec: $(objects)
cc –o fexec $(objects)
unde $(subst .c,.o$(wildcard *.c)) schimbă lista fişierelor sursă cu lista fişierelor obţinută ca urmare a
apelului funcţiei wildcard . Ca urmare , se va crea imaginea executabilă chiar dacă în catalogul curent nu
există un fişiere obiect .

Căutarea dependenţelor .Variabila VPATH şi directiva bpath


In cazul sistemelor mai mari este recomandabil ce conţin cod sursă fie stocate într-un catalog distinct de
cel în care există fişierele binare . Utilitarul make permite căutarea automată a dependenţelor într-o listă de
cataloage , asociată variabilei VPATH . Numele cataloagelor trebuie separate prin două puncte : Căutarea
va avea loc în ordinea în care sânt specificate numele cataloagelor .
De exemplu
VPATH= sursa : ../ include
Specifică o cale ce conţine cataloagele sursa şi . /include .
Ori de câte ori un fişier ce reprezintă o dependenţă nu există în catalogul curent . el va fi căutat în
cataloagele asociate variabilei VPATH . Dacă fişierul este găsit într-unul dintre ele , acel fişier va fi
asociat dependenţei căutare . Deci , în interiorul unei reguli , fişierele vor fi specificate ca şi cum ele există
în catalogul curent .
Similară variabilei VPATH dar mai selectivă ,este directiva vpath
O directivă specifică o acţiune care trebuie executată de make , cum ar fi interpretarea unui alt fişier de
descriere (directiva Include ) sau ignorarea unei anumite zone din fişierul de descriere (directive
condiţionate ).
Directiva vpath permite specificarea unei căi de căutare a unei clase de fişiere , al căror nume se
potriveşte unui tipar dat .
Există trei forme ale directivei vpath :
1) vpath tipar cale
2) vpath tipar
3) vpath
Prima formă specifică calea de căutare pentru fişierele ale căror nume se potrivesc tiparului . Dacă o altă
cale a fost asociată aceluiaşi tipar , noua cale este adăugată efectiv celei anterioare. Calea de căutare este o
listă de cataloage separate prin două puncte :
A doua formă semnifică ştergerea tuturor căilor de căutare anterior asociate tiparului tipar
Cea de a treia formă semnifică ştergerea tuturor căilor de căutare specificate folosind directiva vpath .
Tiparul este un şir de caractere care conţine caracterul %,având o semnificaţie specială . Şirul de caractere
trebuie să se potrivească cu numele fişierului ce este căutat . Caracterul % înlocuieşte o secvenţă de zero
sau mai multe caractere nesemnificative din punct de vedere al potrivirii .Acest caracter poate să lipsească ,
caz în care trebuie să existe o potrivire perfectă , ceea ce nu este , în general folositor .
De exemplu :
Vpath %.C ../surse
Specifică căutarea dependenţelor al căror nume se termină cu caracterele .c
în catalogul ../surse . Directivele vpath sunt prelucrate în ordinea în care apar în fişierul de descriere
.

Căutarea dependenţelor se reflectă în scrierea comenzilor

Atunci când o dependenţă este găsită în alt catalog decât cel curent , acest lucru trebuie să se reflecte şi la
nivelul comenzilor .Deci comenzile trebuie scrise în aşa fel încât să se ia în considerare dependenţele din
cataloagele în care au fost găsite de make.
Acest lucru se poate realiza cu ajutorul variabilelor predefinite $ ^,$@.
Variabila $^are ca valoare lista tuturor dependenţelor unei reguli , inclusiv cataloagele în care au fost găsite
. Variabila $@ reprezintă destinaţia
De exemplu :
fis.o : fis.c
cc –c $^ - o $@
Deseori dependenţelor include şi fişiere antet care nu trebuie menţionate în comenzi . funcţia firstwrd
poate fi utilizată pentru a extrage numai prima dependenţă din lista acestora .
De exemplu ;
VPATH = surse :../include
Fis .o :fis .c inc1.h inc2.h
cc -c $(firstword $^) – o $@
Aici valoarea variabilei $^ poate fi ceva de forma : “surse /fis.c /include/inc2.h “din care funcţia fistword
va extrage doar “surse/fis.c”

Căutarea şi reguli implicite


Si în cazul regulilor implicite funcţionează mecanismul de căutare în lista
cataloagelor specificate prin variabila VPATH sau directiva vpath .
Atunci când un fişier de exemplu fis.o nu are o regulă explicită ,make va considera regulile implicite , cum
ar fi de exemplu compilarea fişierului fis.c dacă acesta există în catalogul curent ,Dacă fişierul fis.c nu
există catalogul curent ,el va fi căutat în lista cataloagelor specificate într-unul di modurile prezentate şi
dacă este găsit compilarea poate avea loc .
Comenzile regulilor implicite utilizează variabilele predefinite , deci căutarea numele fişierelor nu
reprezintă un efort suplimentar .

Căutarea bibliotecilor

Specificarea unei biblioteci ca dependenţă se poate scrie folosind sintaxa : - Inume. Atunci când numele
unei dependenţe are forma anterior menţionată make va căuta fişierul limbnume.a în calculatoare / lib
,/usr/lib, şi în cele specificate de variabila VPATH şi/ sau de directiva vpath.
De exemplu :
Fis: fis .c – libiblo
cc $^ - o $@
va determina execuţia comenzii : cc fis.c –libiblio – o fis dacă fişierul fis are o dată anterioară celei
corespunzătoare fişierelor fis.c sau libiblio.a

Destinaţii fictive

Aşa cum a fost deja menţionat dacă se doreşte scrierea unei reguli ale cărei comenzi
nu vor crea un fişier destinaţie , destinaţia acestei reguli va reprezenta , de fapt
numerele unei secvenţe de comenzi , adică o destinaţie fictivă .
De exemplu :
clean:
rm *.o
comanda rm şterge toate fişierele obiect , ea nu creează un fişier numit clean.
Există totuşi un caz în care comanda nu va mai fi rulată , şi anume atunci când în acelaşi catalog este creat
un fişier pe nume clean . Cum destinaţia clean nu are dependenţe , ea va fi considerată actualizată iar
comenzile corespunzătoare nu vor mai fi rulate .
Pentru a elimina acest caz defavorabil a fost introdusă notaţia .PHONY care determină execuţia
necondiţionată a comenzilor corespunzătoare destinaţiei fictive .
Exemplu nostru devine :
clean :
rm *.o
phony: clear

Destinaţiile fictive pot avea dependenţe.


De exemplu atunci când un catalog conţine mai multe programe , este mai comod şa fie specificate toate
programele în acelaşi fişier de descriere . Având în vedere că destinaţia prelucrată implicit de macke este
prima descrisă, este uzuală specificarea ei ca destinaţie fictivă având ca dependenţe toate programele ce se
doresc create .
Astfel :
all: prog1 prog2 prog 3
.PHONY :all
prog1: $ (objects1)
cc- o prog1 $(objects1)
prog2: $ (objects2)
cc- o prog 2$(objects2)
prog1: $ (objects3)
cc- o prog 3$(objects3)
Pentru a recompila toate programele , este suficient să fie lansat make. Pentru a fi creat doar primul
program se utilizează apelul make prog1.

Fişierele destinaţie vide

Fişierele destinaţie vide reprezintă o variantă a destinaţiilor fictive .Ele sunt utilizate pentru a stoca o
secvenţa de comenzi specifice unei acţiuni care trebuie executată la anumite momente de timp.
Scopul fişierelor destinaţie este de a înregistra ultima dată de execuţie a comenzilor corespunzătoare . st
lucru este realizat prin introducerea în lista comenzilor a unei comenzi touch , de actualizare a fişierului
destinaţie cu data ultimei rulări .
Fişierul destinaţie trebuie să aibă dependenţe , astfel încât comenzile vor fi executate vor fi executate
numai dacă una dintre dependenţe s-a modificat de la ultima execuţie .

De exemplu :
print : fis1 .cfis 2 .c
1pr- $ ?
touch print
Comanda make print va exexuta lpr doar dacă unul dintre fişierele sursă C s-a modificat de la ultima
tipărire. Variabila predefinită $? Are ca valoare lista dependenţelor care sunt mai recente decât destinaţia.

Reguli cu mai multe destinaţii

O regulă cu mai multe destinaţii este echivalentă cu scrierea mai multor reguli care diferă doar din punct de
vedere al destinaţiilor. Acest lucru este util în două cazuri:
1) atunci când se specifică doar dependenţele, de exemplu:
fis1.0 fis.2.0 fis3.0 : incl . h
2) atunci când comenzi similare sunt specificate pentru toate destinaţiile.
In cel de al doilea caz comenzile nu trebuie să fie absolut identice, având în vedere utilizarea variabilelor
automate
De exemplu:
O1gen o2gen : text.g
Gen . gen text .g-$(subst gen, ,$@)> $@
Este echivalent cu a scrie :
olgen : text .g
gen text.g – o1 >o1gen
o2gen : text.g
gen text.g – o2 >o2gen
In exemplul dat , s-a presupus că programul gen produce două tipuri de ieşiri în funcţie de argumentul –o1
sau o2.

Subst este o funcţie, care realizează substituţia textuală a primului argumane cu cel de al doilea în textul
reprezentat de al treilea argument. In exemplul nostru primul argument este şirul de caractere vid,
reprezentat de al doilea argument în textul dat de valoarea variabilei predefinite $@.

8.2.5. Scrierea comenzilor

Comenzile unei reguli sunt executate de către interpretorul de comenzi /bin/sh. Dacă nu este specificat
astfel. Aşa cum a fost anterior menţionat fiecare linie de comandă trebuie să înceapă cu caracterul TAB
excepţie făcând prima regulă care poate fi scrisă pe aceeaşi linie cu dependenţele dar separată de acestea
prin: . O linie de comandă poate fi scrisă pe mai multe linii de text utilizând caracterul \ . la sfârşitul
fiecărei linii text în afară de ultima.
In mod normal make tipăreşte fiecare linie de comandă înainte de a fi executată. Dacă o linie începe cu
caracterul @ comanda nu va fi tipărită înainte de a fi trimisă interpretorului de comenzi. Cazul tipic de
utilizare a acestei facilităţi este atunci când singurul scop al unei comenzi este de a tipări un mesaj.
De exemplu:
@echo Despre fişierele de descriere

Dacă make este apelat cu opţiunea –n , comenzile vor fi doar tipărite pe ecran , ele nu vor fi executate .
acesta este singurul caz în care comenzile care încep cu caracterul @ vor fi tipărite . Această opţiune este
utilă pentru a afla ce comenzi vor fi executate la lansarea utilitarului make. Apelul utilitarului make cu
opţiunea –s are ca efect execuţia comenzilor fără a fi tipărite. Este ca şi cum fiecare linie de comandă ar
începe cu caracterul @

Utilizarea recursivă a lui make

Make poate fi folosit şi ca comandă într-un fişier de descriere. Această tehnică este utilă atunci când există
fişiere de descriere separate pentru fiecare subsistem din cadru unui sistem mare . Datorită unei asemănări
de suprafaţă cu noţiunea de recursivitate din programare în lucrările de specialitate este folosit termenul de
apel “recursive” al lui make El va fi folosit şi în această prezentare
Comenzile de invocare recursive a lui make vor utiliza întotdeauna variabila MAKE şi nu comanda
explicită make. De exemplu , să presupunem că avem un subcatalog subcat care are propriul său fişier de
descriere si dorim ca make sa ruleze în acel catalog . Aceasta se poate specifica într-un fişier de descriere
aflat in catalogul curent , astfel :

subsistem :
cd subcat : $(MAKE)
Valoarea variabilei MAKE este numele fişierului cu care make a fost invocat. Dacă acesta este
/bin/make, atunci comanda executată în exemplu nostru este :
cd supcat ; /bin /make
Dacă la nivelul superior este utilizată o versiune specială a lui make , atunci aceeaşi versiune va fi utilizată
şi pentru invocările recursive .
Utilizarea variabilei MAKE în liniile de comandă ale unei reguli, alterează efectul opţiunilor -t , - n, –q
ale lui make .

Transmiterea variabilelor în apelurile recursive

Valorile variabilelor din contextual apelant sunt transmise unui apel recursive al lui make
ca valori implicite . Ele nu înlocuiesc valorile specificate explicit în fişierul de descriere al make-ului
apelat .
Variabilele sunt transmise numai dacă numele lor constă din litere , cifre şi/sau caracterul _. Utilitarul
make adaugă fiecare variabilă şi valoarea ei contextului de rulare al fiecărei comenzi. Make-ul apelat
utilizează acest context pentru a iniţializa propria sa tabelă de valori ale variabilelor .
Variabilele MAKELEVEL, MAKEFILES , MAKEFLAGS
Variabila MAKELEVEL are ca valoare un număr zecimal care reprezintă nivelul apelurilor recursive .
Utilizarea sa în directive condiţionale permite ca un fişier de descriere să aibă o comportare diferită atunci
când este folosit într-un apel recursiv .
Variabila MAKEFILES este utilizată atunci când se doreşte ca toate comenzile de apel recursiv al lui
make să folosească fişiere de descriere adiţionale. Valoarea variabilei este o listă de nume de fişiere
separate prin spaţii. Acesta variabilă este definită în exterior şi este transmisă în mod uzual prin context.
Variabila MAKEFLAGS este setată automat de make.Ea conţine opţiunile primite de make la apel. O
consecinţă directă este aceea că un make apelat recursive va primi valoarea acestei variabile în contextul
său şi va citi opţiunile transmise prin această variabilă.
Dacă nu se doreşte propagarea opţiunilor, se poate scrie astfel:

MAKEFLAGS=
subsistem:
cd subcat; $(MAKE)
sau:
subsistem:
cd subcat ;$(MAKE) MAKEFLAGS=

Definirea variabilelor pentru secvenţe de comenzi

Atunci când o aceeaşi secvenţă de comenzi este utilizată pentru actualizarea mai multor destinaţii , se poate
defini o variabilă a cărei valoare este secvenţa respectivă de comenzi .
Definirea variabilei se face utilizând directiva devine .
De exemplu ;
define run-yacc
yacc $(firstword $^)
mv y.tab .c $@
endef
unde run-yacc este numele variabilei ce este definită;endef marchează sfârşitul definiţiei; liniile dintre
define şi endef sunt liniile de comandă ce vor fi înlocuite de variabila run-yacc.
Prima comandă din exemplu lansează în execuţie utilitarul yacc , având ca fişier de intrare prima
dependenţă din orice regulă care utilizează această secvenţă de comenzi. Cea de a doua comandă
redenumeşte fişierul y.tab.c (fişierul de ieşire generat de yacc are întotdeauna acest nume ) cu destinaţiei
regulii.
Exemplu de utilizare :

fis.c :fis.y
$(run-yacc)

Erori în comenzi
După execuţia fiecărei comenzi macke verifică modul în care acesta s-a terminat .Dacă terminarea
execuţiei comenzii a fost erori , este lansată comanda următoare ,în caz contrar ,make abandonează
prelucrării curente şi/sau a tuturor regerilor
Uneori terminarea cu eroare a unei comenzi poate să nu indice o problemă . De exemplu comanda mkdir
poate fi utilizată într-un fişier de deschidere pentru a se asigura de existenţa unui catalog . De catalogul
există deja comandă mkdir va raportata eroare , ceea nu ar trebuit să împiedice utilitarul make să continue
.
Pentru a ignora cazurile de eroare intr-o linie de comandă acea linie de comandă trebuie
să înceapă cu caracterul – imediat după tab.
De exemplu :

Clean:
-rm *.o

Utilitarul make poate ignora erorile din toate liniile de comandă ,dacă este apelat cu opţiunea –i. Atunci
când erorile sunt ignorate make tratează ieşirea pe caz de eroare la fel cu ieşirea corectă dar tipăreşte codul
de eroare a fost ignorată.
Dacă la execuţia unei comenzi se semnalizează o eroare consecinţa imediată este că destinaţia nu mai poate
fi reconstituită şi deci nici destinaţiile care depind de aceasta direct sau indirect .Make nu va mai executa
comenzi pentru aceste destinaţii .In mod normal make abandonează prelucrarea fişierului destinaţie . Dacă
este apelat cu opţiunea –k . make va lua în considerare celelalte dependenţe ale acestor destinaţii
reactualizându-le dacă este necesar înainte de a termina cu eroare desigur prelucrarea fişierului destinaţie .
De exemplu după semnalarea unei erori la compilarea unui fişier make –k va continua compilarea
celorlalte fişiere deşi „ştie „ că editarea de legături ne este posibilă .
Scopul principal al utilatorului make este de a actualiza destinaţiile . dacă acest lucru ne este posibil el
raportează eroare imediat . Opţiunea –k arată scopul real este de a testa cât mai multe din modificările
făcute într-un program de a localiza eventual probleme independente care pot fi remediate înainte de o
nouă compilare .

Opţiuni make

Modul de funcţionare al utilitarului make poate fi configurat prin utilizarea opţiunilor transmise în linia de
comandă . Câteva dintre cele mai utile opţiuni sunt prezentate în
Tabelul 8.3 :

Tabelul 8.3

Opţiune Efect
-f permite specificarea unui nume dat de utilizator pentru fişierul de descriere
-i ignoră codurile de eroare întrase de comenzile de descriere
-k continue să lucreze după primirea unui cod de eroare , pe ramurile neafectate
de acestea
-n tipăreşte comenzile dar fără să le execute
-p tipăreşte definiţiile de variabile şi descrierea destinaţiilor : execută comenzile
-q determină dacă destinaţia este deja actualizată ,caz în care întoarce un cod
zero ; nu execută comenzile
-r inhibă utilizarea regurilor implicite
-s inhibă tipărirea liniilor de comandă înainte de a le executa

8.2.6.Comentarii

Pentru marcarea comentariilor într-un fişier este utilizat caracterul # ceea ce determină ignorarea
informaţiei di locul unde este întâlnit şi până la sfârşitul liniei curente . Dacă la sfârşitul liniei curente
apare caracterul de continuare ‚V linia următoare este tot o linie de comentariu . Apariţia comentariilor în
comenzi poate crea probleme pentru că fiind trimise spre execuţie interpetorul de comenzi , acesta are
propria s-a definiţie despre un comentariu .

8.2.8.Un exemplu ilustrat

Pentru a oferi o imagine complectă utilitarului make este prezentat un fişier de descriere utilizat pentru a
translata fişiere sursă Pascal în fişiere sursă C. După obţinerea fişierelor sursă C , este obţinută imaginea
lor executabilă .
Conţinutul său este următorul :
#exemple de utilizare a translatorului Pascal -C „p2c”
cc = cc
p2c=p2c
INC=/usr/include
LIB=/usr/lib/libp2c.a

Implicit : com

#Translatare :
trans :fact.c e.c self.c cref.c basic.c

fact.c : fact.p
$(P2C) e.p

self .c : sef.p
$(P2C) self.p

cref.c :cref.p
$(P2C) cref.p

basic.c :basic.p
$(P2C) basic.c

# Compilare fişiere sursă c:


comp : fact e self cref basic

fact : fact.c
$(CC) -I$(INC) fact .c $(LIB) – o fact
e: e.c
$(CC) - I $(INC) e.c $(LIB) -o e

self : self.c
$(CC) –I$(INC) self.c $(LIB) -o self

cref : cref.c
$(CC) –I$(INC) cref.c $(LIB) -o cref

basic : basic.c
$(CC) –I$(INC) basic.c $(LIB) -lm –o basic

#Sfârsit fişier descriere

Primele linii din fişierul de descriere conţin definiţia variabilelor CC.P2C.INC.LIB a căror semnificaţie
este următoarea :
CC – compilatorul C
P2C – translatorul Pascal – C
INC – catalogul ce conţine fişierele antet necesare compilării unui program C
LIB –biblioteca utilizată pentru crearea imaginii executabile a fişierelor C obţinute în urma procesului de
translatare
Regulile sunt organizate în funcţie de scopul lor. Pentru a obţine translatarea tuturor fişierelor sursă Pascal
specificate e. te descrisă regula :

trans :fact .c e.c self .c cref .c basic.c

trans este o destinaţie fictivă utilizată pentru a permite prelucrarea tuturor fişierelor sursă Pascal .
Desigur fiecare dependenţă a regulii anterior prezentate are propria sa regulă de formare. De exemplu ,
pentru dependenţa fact.c :

fact.c : fact.p
$(P2C) fact.p

Deci fişierul fact .c este obţinut prin prelucrarea fişierului sursă Pascal fact.p utilizând translatorul p2c .
Următorul grup de reguli realizează compilarea fişierelor sursa C obţinute în urma procesului de translatare
.
Pentru compila toate fişierele sursa C, este regula
Comp: fact e self cref basic

în care comp este o destinaţie fictivă.


Fiecare dependenţa a regulii anterior prezentate are propria sa regulă de formare , de exemplu :

fact : fact .c
$(CC) -I$(INC) fact.c $(LIB) -o fact

deci fişierul executabil fact este obţinut prin compilarea fişierului sursă fact.c .
Se observă că apelul utilitarului make cu argumentul comp va determina atât translatarea fişierelor sursă
Pascal cât şi utilizarea obişnuită , prima regulă di fişierul de descriere specifică acest lucru :

Implicit : comp

Implicit este o destinaţie fictivă care are ca dependenţă tot o destinaţie fictivă. Deci comanda make , este
echivalentă cu make comp
Pentru a realiza doar translatarea trebuie apelat make cu argumentul trans.
Dacă se doreşte prelucrarea unui singur fişier , de exemplu fact.p. se pot lansa următoarele comenzi :
) make fact.c pentru a realiza doar translatarea fişierului fact .p
) make fact pentru a realiza ambele prelucrări

8.3.Utilizatorul lint

lint utilitarul destinat programului în limbajul C, care lucrează sub sistemul de operare UNIX .lint verifică
programele scrise în C din punct de vedere al corectitudinii tipurilor de date al restricţiilor de potabilitate ,
al corectitudinii sintactice şi semantice a construcţiilor utilizate . Rularea lint – ului este un pas necesar ,
dar nu suficient , pentru a obţine programe portabile, cu un cod clar . Deşi nu poate rezolva problemele
unui cod prost scris , el se înscrie în tendinţa generală de evoluţie a compilatoarelor de C , de a realiza o
verificare cât mai completă a corectitudinii programelor.

8.3.1.Utilizare lint

lint poate fi apelat ca orice comandă UNIX . Rezultatul rulări este o eventuală listă a nemulţumirilor sale
relativ la programul verificat ., exprimate prin mesaje de avertizare .
De exemplu, dacă fişierul demo.c conţine o_funcţie, definită astfel:

o_funcţie(s)
char *s;
{
void tipăreşte ( );
int i,j;
tipăreşte(”demo Lint”);
}
comanda

$ lint demo
va tipări pe ecran mesaje referitoare la neutralizarea variabilelor i,j şi a argumentului s.
Ca răspuns avertismentele sale, programul trebuie corectat şi rulat lint din nou. Această secvenţă de
operaţii se repetă până când nu mai apar mesaje de eroare pe ecran. Dar lint nu este un căpcăun pentru
liniştea căruia trebuie să-ţi sacrifici programul. A minimiza lista de mesaje a lint-ului nu este un scop în
sine. lint reprezintă doar un ajutor pentru a scrie programe mai portabile, cu un cod mai clar.
Dacă însă se folosesc anumite construcţii despre care programul ştie că sunt mai puţin canonice şi nu
doreşte ca lint să îi reamintească de fiecare dată, el poate comunica utilitarului lint acest lucru. Soluţia
aleasă a fost ca lint să citească comentariile din codul sursă C. Atunci când găseşte un comentariu
cunoscut, lint execută acţiunea asociată comentariului respectiv.
8.3.2. Mesaje lint

În continuare, vor fi prezentate câteva dintre mesajele tipice ale lint-ului, situaţiile în care pot să apară şi
modul în care pot fi remediate erorile sau ignorate avertismentele.

Utilizarea caracterelor neportabile

Pe anumite maşini, în anumite implementări ale limbajului C, caracterele sunt mărimi cu semn, cu valori în
plaja [-128,127]. În alte implementări, caracterele pot primi numai valori pozitive. De aceea lint va tipări
mesaje despre anumite comparaţii şi atribuiri, ca fiind neportabile.
De exemplu:

Char c;
If ((c=getchar( )) <0)

Nu va funcţiona pe maşinile pe care caracterele primesc numai valori pozitive. lint va tipări mesajul:

”nonportable caracter comparison”

Soluţia este de a declara variabila c de tip întreg .


O soluţie similară poate apare în cazul utilizării câmpurilor de biţi. La atribuirea unei constante unui câmp,
acesta poate fi prea mic pentru a stoca acea constantă. Problema se manifestă în special pe maşinile în care
aceste câmpuri prezintă mărimi de semn. De exemplu, deşi pare logic că un câmp format de doi biţi, de tip
întreg, să nu poată stoca valoarea trei, problema câmpului este unsigned.

Variabila şi/sau funcţii definite neutilizate

Pe măsură ce un program este dezvoltat, pot apare variabile şi/sau funcţii care nu sunt necesare, dar nu sunt
eliminate din codul sursă. Deşi aceste erori nu pot constitui, în general, cauza unei funcţionări incorecte a
programului, ele conduc la generări ineficiente de cod şi fac programele greu de înţeles şi modificat.
Atenţie, însă, la codul compilat condiţionat, folosind directiva #ifdef. S-ar putea să fie o înlănţuire greşită a
condiţiilor şi, ca urmare, o verificare doar pe o porţiune a codului, care se dorea în întregime verificat.
Acest mesaj mai poate apare şi atunci când un fişier sursă C include un fişier antet în care există
declaraţiile unor funcţii care nu sunt apelate în fişierul sursă respectiv. Mesajele lint despre variabile
definite dar neutilizate pot fi suprimate scriind comentariul /*LINTBRARY*/ la începutul fişierului. În
anumite contexte (vezi secţiunea ”Opţiunea lint”) acelaşi lucru poate fi obţinut utilizând opţiunile –u sau –
x.

Variabile utilizate dar nedefinite

lint încearcă să descopere cazurile în care o variabilă este utilizată înainte de a fi iniţializată. Pentru că
variabilele globale şi statice sunt iniţializate cu zero la declaraţie, el detectează variabilele locale (de tip
registru sau automatice) a căror utilizare apare, fizic, în fişier înaintea atribuiri.

Număr variabil de argumente


În general, există două motive pentru care este generat acest mesaj: un număr diferit de argumente la
definirea, respectiv utilizarea unei funcţii şi scrierea unor funcţii şi scrierea unor funcţii cu număr variabil
de argumente.
Uneori apare necesitatea scrierii unei funcţii cu o interferenţă în care anumite argumente sunt opţionale. O
astfel de funcţie poate fi proiectată să îndeplinească diferite scopuri, în funcţie de argumentele utilizate.
Implicit, lint tipăreşte mesaje despre argumentele neutilizate. Pentru a suprima tipărirea mesajelor, lint
trebuie apelat cu opţiunea –v, care are efect asupra întregului fişier sursă C analizat de lint. Dacă, însă, se
doreşte suprimarea mesajelor doar în anumite funcţii, trebuie adăugat comentariul /*ARGSUSED*/
înaintea codului corespunzător acelei funcţii.
Deşi nu este o practică recomandabilă, este totuşi permisă scrierea unor funcţii cu număr variabil de
argumente, de tipul printf. Cele mai multe sisteme UNIX conţin fişierul antet <varargs.h> care asigură
portabilitate în cazul scrierii unui cod care depinde de un număr variabil de argumente. Dacă, însă,
programatorul doreşte să scrie o funcţie cu număr variabil de parametrii, neportabilă, şi nu îl interesează
mesajele utilitarului lint, le poate suprima prin scrierea comentariului /*VARARGS*/ înaintea codului
funcţiei respective. În anumite cazuri, poate fi totuşi necesară, verificarea primelor n argumente, ceea ce se
realizează utilizând forma /*VARARGSn*/.

Argument neutilizat în funcţie

Acest mesaj apare atunci când un argument nu este utilizat în codul funcţiei. Dacă acest argument nu
trebuie utilizat, atunci poate fi scris comentariul /*ARGSUSED*/ înaintea codului funcţiei, pentru a
suprima mesajele lint referitor la argumente neutilizate.

Redefinire variabilă

Acest mesaj apare atunci când într-un program există o variabilă globală şi o variabilă locală sau un
argument al unei funcţii cu acelaşi nume. Din punct de vedere al întreţinerii unui program nu este o
practică recomandabilă. De aceea este bine să fie schimbate numele variabilelor, prin infixarea lor, de
exemplu, dacă există o legătură logică între ele.

Argument utilizat inconsistent

Utilizarea unui alt tip de date pentru acelaşi argument în apeluri diferite ale unei funcţii determină
generarea acestui mesaj. În general, este vorba despre o neatenţie în scrierea codului. De exemplu, apel de
forma: fprintf(”mesaj”), care nu specifică fişierul destinaţie, adică primul argument al funcţiei fprintf.

Valoarea declarată inconsistent

Atunci când tipăreşte acest mesaj, lint se referă la utilizarea unei funcţii care întoarce o valoare de alt tip
decât cel aşteptat. Acest lucru poate apare, în general, dacă există două funcţii cu acelaşi nume, sau dacă
fişierul în care este apelată funcţia a fost omisă includerea prototipului ei.

Valoarea întoarsă de o funcţie este ignorată

Acest mesaj apare atunci când nu este folosită valoarea întoarsă de o anumită funcţie. Un exemplu tipic
este: fprintf(stderr,…). Dacă apare o eroare la scrierea stderr, nu se mai pune problema de a informa
utilizatorul despre această eroare, având în vedere starea fişierului. Deci, valoarea întoarsă de fprintf nu
este utilizată. Pentru ca lint să nu genereze mesaje, o soluţie este de a pune un cast void la apelul funcţiei.

Funcţia conţine return; şi return(expr);

Dacă pe o ramură a unei funcţii este întoarsă o valoare, dar pe o altă ramură nu se întoarce nici o valoare,
atunci este generat acest mesaj. Deseori el este însoţit de mesaje referitoare la valori întoarse, dar
nefolosite. Un exemplu tipic este o funcţie care întoarce –1 dacă o eroare este detectată, dar nu întoarce
nimic, dacă totul merge bine.
lint nu poate detecta funcţii care sunt apelate, dar nu mai întorc controlul, cum este de exemplu, exit.
Astfel, apelul funcţiei exit va genera un cod inaccesibil, nedetectabil de către lint. Implicaţia cea mai
serioasă este în determinarea valorii întoarse de o funcţie. Dacă o funcţie, pe o anumită ramură, nu întoarce
nici o valoare, pentru simplul motiv că apelează o funcţie de tip exit, lint nu poate detecta acest lucru şi
generează mesaje, când de fapt nu este nimic greşit. Pentru a-l informa pe lint că o anumită porţiune de cod
este inaccesibilă, poate fi adăugat comentariul /*NOTREACHED*/ în locul respectiv.
Să vedem ce se întâmplă atunci când lint analizează fişierul testexit.c, al cărui conţinut este următorul:

/*
* citeşte intrări de la tty, se termină dacă tastează q
*/

#include <stdio.h>

char
ttyin( )
{
char buf[BUFIZ]
FILE *efopen ( )
void exit ( )
static FILE *tty = NULL;

if (tty = = NULL)
tty = efopen (”/dev/tty”, ”r”);
if (fgets (buf, BUFSIZ, tty) = = NULL | | buf [0] = = ’q’)
exit (0);
else
return buf [0];
}

Analiza acestui cod va duce la generarea următorului mesaj:


”function ttyin has return(e): and return;”.
Aşa cum am văzut, soluţia este de a utiliza comentariul /*NOTRECHED*/ pentru a marca zona
respectivă de cod ca inaccesibilă şi deci a suprima mesajul lint.

/*
* citeşte intrări de la tty, de termină dacă se tastează q
*/
#include <stdio.h>

char
ttyin ( )
{
Char buf [BUFSIZ];
FILE *efopen ( );
Void exit( );
Static FILE * tty = NULL;

If (tty == NULL
Tty = efopen ( ”/dev / tty” , ” r ”);
If (fgets (buf, BUFSIZ, tty) == NULL buf [ 0 ] == ’q’ )
Exit ( 0 );
/ *NOTREACHED* /
else
return buf [ 0 ];

Ordine de evaluare nedefinitã

Limbajul C nu specificã ce se întâmplã atunci când o variabilã este utilizatã şi modificatã în aceeaşi
expresie.

De exemplu:

int i , *a, *b;


A [i] = b [i++];

Cel care scrie compilatorul este liber sã-l incrementeze pe i în orice punct al evaluării. Deşi este probabil
de aşteptat ca lucrurile sã decurgă astfel:
• Salvează b i într-o variabilã temporarã temp
• Incrementează i
• Atribuie a[i] = temp
dar limbajul C nu specificã acest lucru.
Codul care depinde de o ordine particularã de evaluare nu este portabil, astfel încât lint va genera mesaje la
întâlnirea unei situaţii de acest fel. Soluţia este de a specifica explicit ordinea de evaluare doritã, detaliind
puţin codul, de exemplu:

int i, *a, *b;


a [ i ] = b [ i ];
i ++;
Opţiuni lint

lint este invocat ca orice comandã UNIX, respectiv:


Lint opţiuni fişiere descr_biblioteci
opţiuni – reprezintă lista opţiunilor lint
fişiere – reprezintă numele fişierelor care trebuie verificate de lint, al căror sufix este.c sau .ln
descr_biblioteci – reprezintă numele bibliotecilor care sunt utilizate pentru verificarea unui program.
Opţiunile cele mai des utilizate sunt prezentate în tabelul 8.4:
Tabelul 8.4

Opţiuni Efect
a suprimă mesajele referitoare la atribuirea valorilor long unor variabile care nu sunt de tip long

-b suprimã mesajele referitoare la instrucţiuni break inaccesibile

h inhibã aplicarea euristicilor – verificări care permit detectarea erorilor determinate de înţelegerea incorectã
a anumitor noţiuni ale limbajului C, cât şi al unui stil de programare mai puţin elegant
n nu face verificări de compatibilitate cu biblioteca standard lint
p suprimã mesajele despre funcţii şi variabile externe utilizate şi nedefinite sau definite şi neutilizate.
v suprimã mesajele despre argumente neutilizate în funcţii
x suprimã mesajele despre variabile prin declaraţii extern dar neutilizate

Datoritã diferenţele de implementarea opţiunilor lint pentru diferitele sisteme UNIX, este bine sã consultaţi
documentaţia sistemului dumneavoastră.
O bibliotecã lint este o descriere în termeni proprii lint a detaliilor despre un set de funcţii C lint.
Funcţionează în doi paşi: analiza sintacticã şi verificarea referinţelor între module. O bibliotecã lint este
rezultatul execuţiei primului pas pentru toate funcţiile unei biblioteci C. Acest rezultat este memorat într-un
fişier pe disc, ce poate fi utilizat mai târziu, atunci când este analizat de lint programul ce utilizează acea
bibliotecã.

Utilizarea lint într-un fişier makefile

Modul standard de a apela utilitarul lint într-un fişier makefile este urmãtorul:
LINTFLAGS = # opţiuni valide pentru sistemul dumneavoastră
LINTLIBS = # se scrie orice bibliotecã lint localã
SRCS = # fişierele sursã .c care trebuie verificate

lint: $ (SRCS)
lint $ (LINTFLAGS) $ (SRCS) $ (LINTLIBS)

Dacã este probabil sã apară multe mesaje de avertizare, atunci acestea pot fi stocate într-un fişier, prin
redirectarea ieşirii implicite. Aceasta se poate specifica în douã moduri:

a) în fişierul de descriere:
lint: $ (SRCS)
lint $ (LINTFLAGS) $ SRCS) $ (LINTLIBS) > lint.out
ceea ce va determina memorarea mesajelor de avertizare în fişierul lint.ou
b) make lint tee lint . out

ceea ce va determina atât tipărirea mesajelor, cât şi memorarea lor în fişierul lint.out.