Laboratoare:Introducere

= Introducere =

Laboratorul de Sisteme de Operare este unul de programare de sistem având drept scop aprofundarea conceptelor prezentate la curs şi prezentarea interfeţelor de programare oferite de sistemele de operare (system API).

Un laborator va aborda un anumit set de concepte şi va conţine:
 * un scurt breviar teoretic
 * o prezentare a API-ului asociat cu explicaţii şi exemple
 * un set de exerciţii pentru acomodarea cu acesta.

Vă recomandăm insistent să parcurgeţi textul laboratorului de acasă.

Suportul de laborator este alcătuit din:
 * Linux
 * Linux System Programming
 * Advanced Programming in the UNIX environment, 2nd Edition


 * Windows
 * Windows System Programming, 3rd Edition
 * Programming Applications for Microsoft Windows, 4th Edition


 * lista de discuţii
 * canalul de IRC dedicat cursului.

Pentru a oferi o arie de cuprindere cât mai largă, laboratoarele au ca suport familiile de sisteme de operare Unix şi Windows. Instanţele de sisteme de operare din familiile de mai sus alese pentru acest laborator sunt GNU/Linux, respectiv Windows XP Service Pack 2.

În cadrul acestui laborator (laboratorul de introducere), va fi prezentat mediului de lucru care va fi folosit în cadrul laboratorului de Sisteme de Operare. Laboratorul foloseşte ca suport de programare limbajul C/C++. Pentru GNU/Linux se va folosi suita de compilatoare GCC, iar pentru Windows compilatorul Microsoft pentru C/C++ cl. De asemenea, pentru compilarea incrementală a surselor se vor folosi GNU make (Linux), respectiv nmake (Windows). Exceptând apelurile de bibliotecă standard, API-ul folosit va fi POSIX, respectiv Win32.

= Linux =

GCC
GCC este suita de compilatoare implicită pe majoritatea distribuţiilor Linux. GCC este unul din primele pachete software dezvoltate de organizaţia "Free Software Fundation" în cadrul proiectului GNU (Gnu's Not Unix). Proiectul GNU a fost iniţiat ca un protest împotriva software-ului proprietar de Richard Stallman la începutul anilor '80.

La început, GCC se traducea prin "GNU C Compiler", pentru ca iniţial scopul proiectului GCC era dezvoltarea unui compilator C portabil pe platforme UNIX. Ulterior, proiectul a evoluat astăzi fiind un compilator multi-frontend, multi-backend cu suport pentru limbajele C, C++, Objective-C, Fortran, Java, Ada. Drept urmare, acronimul GCC înseamnă, astăzi, "GNU Compiler Collection".

La numărul impresionant de limbaje de mai sus se adaugă şi numărul mare de platforme suportate atât din punctul de vedere al arhitecturii hardware (i386, alpha, vax, m68k, sparc, HPPA, arm, MIPS, PowerPC, etc.) cât şi al sistemelor de operare (GNU/Linux, DOS, Windows 9x/NT/2000, *BSD, Solaris, Tru64, VMS, etc.). La ora actuală, GCC-ul este cel mai portat compilator.

În cadrul laboratoarelor de Sisteme de Operare ne vom concentra asupra facilităţilor oferite de compilator pentru limbajul C/C++. GCC suportă stadardele ANSI, ISO C, ISO C99, POSIX dar şi multe extensii folositoare care nu sunt incluse în niciunul din standarde; unele din aceste extensii vor fi prezentate în secţiunile ce urmează.

Utilizare GCC
O vedere de ansamblu asupra procesului de compilare este prezentata in imaginea de mai jos.



GCC foloseşte pentru compilarea de programe C/C++ comanda gcc, respectiv g++. O invocare tipică este pentru compilarea unui program dintr-un singur fişier sursă:

$ gcc hello.c $ ./a.out Hello, world!

Comanda gcc hello.c a fost folosită pentru compilarea fişierului sursă hello.c. Rezultatul a fost obţinerea executabilului a.out care a fost rulat.

Executabilul a.out este executabilul implicit obţinut de gcc. Dacă se doreşte obţinerea unui executabil cu alt nume se poate folosi opţiunea -o:

$ gcc hello.c -o hello $ ./hello Hello, world!

Comanda de mai sus a produs executabilul hello.

La fel se poate folosi g++ pentru compilarea unui fişier sursă C++:

$ g++ hello.cpp -o hello_cpp $ ./hello_cpp Hello, world!

Opţiuni
După cum s-a observat, la o rulare a comenzii gcc</tt>/g++</tt> se obţine din fişierul sursă un executabil. Folosind diverse opţiuni, putem opri compilarea la una din fazele intermediare astfel:

La opţiunile de mai sus se poate folosi opţiunea -o pentru specificarea fişierului de ieşire:
 * -E - se realizează doar preprocesarea fişierului sursă
 * gcc -E hello.c, se va obtine fisierul preprocesat hello.i</tt>
 * -S - se realizează inclusiv faza de compilare
 * gcc -S hello.c, se va obtine fisierul in limbaj de asamblare hello.s</tt>
 * -c - se realizează inclusiv faza de asamblare
 * gcc -c hello.c, se va obtine fisierul obiect  hello.o </tt>

$ gcc -c hello.c -o my_obj_hello.o

Activarea avertismentelor (warnings)
În mod implicit, o rulare a gcc oferă puţine avertismente utilizatorului. Pentru a activa afişarea de avertismente se foloseşte opţiunea -W cu sintaxa -Woptiune-warning</tt>. optiune-warning</tt> poate lua mai multe valori posibile printre care return-type</tt>, switch</tt>, unused-variable</tt>, uninitialized</tt>, implicit</tt>, all</tt>. Folosirea opţiunii -Wall</tt> înseamnă afişarea tuturor avertismentelor care pot cauza inconsistenţe la rulare.

Considerăm ca fiind indispensabilă folosirea opţiunii -Wall</tt> pentru a putea detecta încă din momentul compilării posibilele erori. O cauză importantă a apariţiilor acestor erori o constituie sintaxa foarte permisivă a limbajului C. Sperăm ca exemplul de mai jos să justifice utilitatea folosirii opţiunii -Wall</tt>:

'''Exemplu 1. intro-01.c''' În exemplul mai sus, programatorul a uitat că funcţia definită de el pentru adunare primeşte trei parametri şi nu doi. Dacă programul se compilează fără opţiunea -Wall, nu se vor genera erori sau avertismente, dar rezultatul nu va fi cel aşteptat:

$ gcc intro-01.c $ ./a.out 1+2 fac -1073743413

Prgramul s-a compilat fără erori, pentru că funcţia <tt>suma</tt> a fost declarată implicit de compilator (în C, în mod normal, funcţiile trebuie să fie declarate înainte de a fi folosite). O funcţie declaratã implicit are prototipul:

int function(...);

În prototipul de mai sus se poate recunoaşte operatorul <tt>...</tt> (se citeşte elipses) care precizează faptul că funcţia are un număr variabil de parametri. Dacă se compilează acelaşi program folosind optiunea <tt>-Wall</tt>, programatorul va avea cel puţin ocazia să afle că funcţia a fost declarată implicit (şi, în cazul de faţă, şi faptul că a uitat să întoarcă un rezultat din funcţia <tt>main</tt>):

$ gcc intro-01.c -Wall exemplul-1.c: In function `main': exemplul-1.c:5: warning: implicit declaration of function `suma' exemplul-1.c:6: warning: control reaches end of non-void function

Soluţia este crearea unei declaraţii pentru funcţia <tt>suma</tt> şi apelul corespunzător al acesteia:

'''Exemplu 2. intro-02.c''' $ gcc -Wall intro-02.c $ ./a.out 1 + 2 fac 3

Exemplul prezentat oferă doar una din erorile posibile pe care GCC le detectează atunci când se foloseşte optiunea <tt>-Wall</tt>. În concluzie, folositi opţiunea <tt>-Wall</tt>. În aceeaşi categorie, mai exista opţiunea <tt>-Wextra</tt> (echivalent cu opţiunea <tt>-W</tt>), mult mai agresivă.

Alte opţiuni
Alte opţiuni utile sunt:


 * -Lcale - aceastã optiune instruieşte compilatorul sã caute si în directorul cale bibliotecile pe care trebuie sã le foloseascã programul; opţiunea se poate specifica de mai multe ori, pentru a adãuga mai multe directoare
 * -lbiblioteca - instruieşte compilatorul cã programul are nevoie de biblioteca biblioteca. Fişierul ce conţine biblioteca va fi denumit libbiblioteca.so sau libbiblioteca.a.
 * -Icale - instruieşte compilatorul sã caute fişierele antet (headere) şi în directorul cale; opţiunea se poate specifica de mai multe ori, pentru a adãuga mai multe directoare
 * -Onivel-optimizari - instuieşte compilatorul ce nivel de optimizare trebuie aplicat; -O0 va determina compilatorul sã nu optimizeze codul generat; -O3 va determina compilatorul sã optimizeze la maxim codul generat; -O2 este pragul de unde compilatorul va începe sã insereze direct în cod functiile inline în loc sã le apeleze; -Os va optimiza programul pentru a reduce dimensiunea codului generat, si nu pentru viteză.
 * -g - dacã se foloseşte aceastã opţiune compilatorul va genera în fişierele de ieşire informaţii care pot fi apoi folosite de un debugger (informaţii despre fişierele sursã şi o mapare între codul maşinã şi liniile de cod ale fişierelor sursã)

Paginile de ajutor ale GCC (man gcc, info gcc) oferă o listă cu toate opţiunile posibile ale GCC.

Compilarea din mai multe fişiere
Exemplele de până acum tratează programe scrise într-un singur fişier sursă. În realitate, aplicaţiile sunt complexe şi scrierea întregului cod într-un singur fişier îl face greu de menţinut şi greu de extins. În acest sens aplicaţia este scrisă în mai multe fişiere sursă denumite module. Un modul conţine, în mod obişnuit, funcţii care îndeplinesc un rol comun.

Următoarele fişiere sunt folosite ca suport pentru a exemplifica modul de compilare a unui program provenind din mai multe fişiere sursă:

'''Exemplu 3. intro-03-main.c'''

'''Exemplu 3. intro-03-util.h''' '''Exemplu 3. intro-03-f1.c''' '''Exemplu 3. intro-03-f2.c''' În programul de mai sus se apelează, respectiv, funcţiile <tt>f1</tt> şi <tt>f2</tt> în funcţia <tt>main</tt> pentru a afişa diverse informaţii. Pentru compilarea acestora se transmit toate fişierele C ca argumente comenzii <tt>gcc</tt>:

$ gcc -Wall intro-03-main.c intro-03-f1.c intro-03-f2.c -o intro-03 $ ./intro-03 Fisierul curent este intro-03-f1.c Va aflati la linia 5 din fisierul intro-03-f2.c

Executabilul de ieşire a fost denumit intro-03; pentru acest lucru s-a folosit opţiunea <tt>-o</tt>.

Se observă folosirea fişierului header intro-03-util.h pentru declararea funcţiilor f1 şi f2. Declararea unei funcţii se realizează prin precizarea antetului. Fişierul header este inclus în fişierul intro-03-main.c pentru ca acesta să aibă cunoştinţă de formatul de apel al funcţiilor f1 şi f2. Funcţiile f1 şi f2 sunt definite, respectiv, în fişierele intro-03-f1 şi intro-03-f2. Codul acestora este integrat în executabil în momentul link-editării.

În general în obţinerea unui executabil din surse multiple se obişnuieşte compilarea fiecărei surse până la modulul obiect şi apoi link-editarea acestora:

$ gcc -Wall -c intro-03-f1.c $ gcc -Wall -c intro-03-f2.c $ gcc -Wall -c intro-03-main.c $ gcc intro-03-f1.o intro-03-f2.o intro-03-main.o -o intro-03-m $ ./intro-03-m Fisierul curent este intro-03-f1.c Va aflati la linia 5 din fisierul intro-03-f2.c

Se observă obţinerea executabilului intro-03-m prin legarea modulelor obiect. Această abordare are avantajul eficienţei. Dacă se modifică fişierul sursă intro-03-f2 atunci doar acesta va trebui compilat şi refăcută link-editarea. Dacă s-ar fi obţinut un executabil direct din surse atunci s-ar fi compilat toate cele trei fişiere şi apoi refăcută link-editarea. Timpul consumat ar fi mult mai mare, în special în perioada de dezvoltare când fazele de compilare sunt dese şi se doreşte compilarea doar a fişierelor sursă modificate.

Scăderea timpului de dezvoltare prin compilarea numai a surselor care au fost modificate este motivaţia de bază pentru existenţa utilitarelor de automatizare precum make sau nmake.

Un lucru important în utilizarea header-elor pentru aplicaţii cu mai multe fişiere este folosirea directivelor de procesare #ifndef, #define, #endif prezentate în secţiunea următoare. Un fişier header tipic va avea structura: Aceste directive de preprocesare au rolul de a proteja declaraţiile din header în cazul în care acesta este inclus de mai multe ori. Astfel, la prima includere nu va fi definit _NUME_HEADER_H (#ifndef), drept pentru care se defineşte _NUME_HEADER_H (#define) şi se prelucrează diversele declaraţii. La următoarea includere _NUME_HEADER_H va fi deja definit (#ifndef) şi nu va mai fi prelucrată partea de declaraţii, evitându-se astfel generarea unor erori de genul "multiple declaration". De remarcat că, pentru fişiere antet diferite este necesar ca simbolurile declarate la început, după modelul de mai sus, să fie diferite; altfel este posibil ca declaraţiile din al doilea antet protejat să fie în mod eronat omise după preprocesare.

Directivele de preprocesare __FILE__ şi __LINE__ sunt expandate de preprocesor la numele fişierului, respectiv numărul liniei.

Preprocesorul. Opţiuni de preprocesare
Preprocesorul este prima componentă apelată în momentul folosirii comenzii gcc. Preprocesorul pe distribuţiile Linux este GNU CPP. După CPP se apelează compilatorul efectiv (GCC), apoi asamblorul (GAS) şi apoi linker-ul (GNU LD). Rolul CPP este acela de prelucrare a directivelor şi a operatorilor de preprocesare.

Directivele de preprocesare cele mai întâlnite sunt:


 * #include pentru includerea de fişiere (de obicei header) într-un alt fişier


 * #define, #undef folosite pentru definirea, respectiv anularea definirii de macrouri
 * #if, #ifdef, #ifndef, #else, #elif, #endif folosite pentru compilare condiţionată
 * __FILE__, __LINE__, __func__ sunt înlocuite cu numele fişierului, linia curentă în fişier şi numele funcţiei


 * operatorul # este folosit pentru a înlocui o variabilă transmisă unui macro cu numele acesteia

'''Exemplu 4. intro-04.c''' $ gcc -Wall intro-04.c $ ./a.out variabila my_uber_var are valoarea 12345


 * operatorul ## (token paste) este folosit pentru concatenarea între un argument al macrodefiniţiei şi un alt şir de caractere sau între două argumente ale macrodefiniţiei.

Opţiuni pentru preprocesor la apelul gcc
Preprocesorului îi pot fi transmise opţiuni prin parametri transmişi comenzii gcc. Pentru aceasta se pot utiliza opţiunile -I sau -D.

Opţiunea -I este utilă pentru a preciza locul în care se află fişierele incluse. Astfel, daca fişierul header <tt>utils.h</tt> se află în directorul <tt>includes/</tt>, utilizatorul poate include fişierul în forma


 * 1) include "utils.h"

dar va trebui să precizeze calea către fişier folosind opţiunea -I:

$ gcc -Iincludes [...]

Opţiunea -D este utilă pentru a defini macrouri în linia de comandă:

$ gcc -D __DEBUG__ [...] $ gcc -D SIMPLE_MACRO=10 [...]          ; echivalent cu #define SIMPLE_MACRO    10

Opţiunea -U este utilă pentru a anula definirea unui macro.

Debugging folosind directive de preprocesare
De multe ori, un dezvoltator va dori să poată activa sau dezactiva foarte facil afişarea de mesaje suplimentare (de informare sau de debug) în sursele sale. Metoda cea mai simplă pentru a realiza acest lucru este prin intermediul unui macro: Dacă se foloseşte opţiunea -D în linia de comandă, atunci definiţia macroului DEBUG poate fi eliminată:

$ gcc -DDEBUG [...]

Folosirea perechii de directive #ifdef, #endif prezintă dezavantajul încărcării codului. Se poate încerca modularizarea afişării mesajelor de debug printr-o construcţie de forma: În momentul de faţă problema este folosirea mai multor argumente la printf. Acest lucru poate fi rezolvat prin intermediul macrourilor cu număr variabil de parametri sau variadic macros, apărute în standardul ISO C99: Singura problema care mai poate apărea este folosirea Dprintf cu un singur argument. În acest caz macroul se expandează la printf (msg,), expresie invalidă în C. Pentru a elimina acest incovenient se foloseşte operatorul ##. Dacă acesta este folosit peste un argument care nu există, atunci virgula se elimină şi expresia devine corectă. Acest lucru nu se întâmplă în cazul în care argumentul există (altfel spus operatorul ## nu schimbă sensul de până atunci): Un ultim retuş este afişarea, dacă se doreşte, a fişierului şi liniei unde s-a apelat macroul:

Linker-ul. Opţiuni de link-editare. Biblioteci
Linker-ul este folosit pentru a "unifica" mai multe module obiect şi biblioteci şi a obţine un executabil sau o bibliotecă. Linker-ul are rolul de a rezolva simbolurile nedefinite dintr-un modul obiect prin inspectarea celor existente într-un altul. Erorile de linker apar ca urmare a lipsei unui simbol, ca în exemplul de mai jos:

'''Exemplu 5. intro-05-main.c''' '''Exemplu 5. intro-05-f.c''' $ gcc -Wall intro-05-main.c /tmp/ccVBU35X.o: In function `main': intro-05-main.c:(.text+0x12): undefined reference to `f' collect2: ld returned 1 exit status $ gcc -Wall intro-05-main.c intro-05-f.c $ ./a.out Hello, World!

La o primă rulare a apărut eroare pentru că linker-ul nu a găsit funcţia f. Includerea intro-05-f.c în lista de fişiere compilate a rezolvat această problemă.

Linker-ul pe distribuţiile Linux este GNU LD. Executabilul asociat este ld. De obicei comanda gcc apelează în spate ld pentru a efectua link-editarea modulelor obiect. Opţiunile de linking sunt de obicei transmise comenzii gcc. Opţiunile cele mai utile sunt cele care sunt legate de biblioteci.

Biblioteci
O bibliotecă este o colecţie de funcţii precompilate. În momentul în care un program are nevoie de o funcţie, linker-ul va apela respectiva funcţie din bibliotecă. Numele fişierului reprezentând biblioteca trebuie să aibă prefixul lib: $ ls -l /usr/lib/libm.* -rw-r--r-- 1 root root 481574 Jul 30 23:41 /usr/lib/libm.a lrwxrwxrwx 1 root root    14 Aug 25 20:20 /usr/lib/libm.so -> /lib/libm.so.6

Biblioteca matematică este denumită libm.a sau libm.so. În Linux bibliotecile sunt de două tipuri: Detalii despre crearea bibliotecilor se găsesc în secţiunea următoare.
 * statice, au de obicei, extensia .a
 * dinamice, au extensia .so

Legarea se face folosind opţiunea -l transmisă comenzii gcc. Astfel, dacă se doreşte folosirea unor funcţii din math.h, trebuie legată biblioteca matematică:

'''Exemplu 6. intro-06.c''' $ gcc -Wall intro-06.c /tmp/ccRqG57V.o: In function `main': intro-06.c:(.text+0x1b): undefined reference to `cos' intro-06.c:(.text+0x2c): undefined reference to `sin' collect2: ld returned 1 exit status $ gcc -Wall intro-06.c -lm $ ./a.out sin = 0.707107, cos = 0.707107

Se observă că, în primă fază, nu s-au rezolvat simbolurile cos şi sin. După legarea bibliotecii matematice, programul s-a compilat şi a rulat fără probleme.

Crearea de biblioteci
Pentru crearea de biblioteci vom folosi exemplul 3. Vom include modulele obiect rezultate din fişierele sursă intro-03-f1.c şi intro-03-f2.c într-o bibliotecă pe care o vom folosi ulterior pentru obţinerea executabilului final.

Primul pas constă în obţinerea modulelor obiect asociate:

$ gcc -Wall -c intro-03-f1.c $ gcc -Wall -c intro-03-f2.c

Crearea unei biblioteci statice
O biblioteca statica este o arhiva ce contine fisiere obiect creata cu ajutorul utilitarului ar.

$ ar rc libintro.a intro-03-f1.o intro-03-f2.o $ gcc -Wall intro-03-main.c -o intro-lib -lintro /usr/bin/ld: cannot find -lintro collect2: ld returned 1 exit status

Aruncati o privire in pagina de manual a utilitarului <tt> ar </tt> si interpretati parametrii rc de mai sus.

Linker-ul returnează eroare precizând că nu găseşte biblioteca libintro. Aceasta deoarece linker-ul nu a fost configurat să caute şi în directorul curent. Pentru aceasta se foloseşte opţiunea -L, urmată de directorul în care trebuie căutată biblioteca (în cazul nostru este vorba de directorul curent):

$ gcc -Wall intro-03-main.c -o intro-lib -lintro -L. $ ./intro-lib Fisierul curent este intro-03-f1.c Va aflati la linia 5 din fisierul intro-03-f2.c

Crearea unei biblioteci partajate
Spre deosebire de o biblioteca statica despre care am vazut ca nu este nimic altceva decat o arhiva de fisiere obiect, o biblioteca partajata este ea insasi un fisier obiect. Crearea unei biblioteci partajate se realizează prin intermediul linker-ului. Optiunea -shared indica compilatorului sa creeze un obiect partajat si nu un fisier executabil. Este, de asemenea, indicată folosirea opţiunii -fPIC:

$ gcc -shared -fPIC intro-03-f1.o intro-03-f2.o -o libintro_shared.so $ gcc -Wall intro-03-main.c -o intro-lib -lintro_shared -L. $ ./intro-lib ./intro-lib: error while loading shared libraries: libintro_shared.so: cannot open shared object file: No such file or directory

La rularea executabilului se poate observa că nu se poate încărca biblioteca partajată. Cauza este deosebirea dintre bibliotecile statice şi bibliotecile partajate. În cazul bibliotecilor statice codul funcţiei de bibliotecă este copiat în codul executabil la link-editare. De partea cealaltă, în cazul bibliotecilor partajate, codul este încărcat în memorie în momentul rulării.

Astfel, în momentul rulării unui program, loader-ul (programul responsabil cu încărcarea programului în memorie), trebuie să ştie unde să caute biblioteca partajată pentru a o încărca în memorie în cazul în care aceasta nu a fost încărcată deja. Loader-ul foloseşte câteva căi predefinite (/lib, /usr/lib, etc) şi de asemenea locaţii definite în variabila de mediu LD_LIBRARY_PATH:

$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. $ ./intro-lib Fisierul curent este intro-03-f1.c Va aflati la linia 5 din fisierul intro-03-f2.c

În exemplul de mai sus variabilei de mediu LD_LIBRARY_PATH i-a fost adăugată calea către directorul curent rezultând în posibilitatea rulării programului. LD_LIBRARY_PATH va rămâne modificată cât timp va rula consola curentă. Pentru a face o modificare a unei variabile de mediu doar pentru o instanță a unui program se face atribuirea noii valori înaintea comenzii de execuție:

$ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. ./intro-lib Fisierul curent este intro-03-f1.c Va aflati la linia 5 din fisierul intro-03-f2.c $ ./intro-lib ./intro-lib: error while loading shared libraries: libintro_shared.so: cannot open shared object file: No such file or directory

GNU Make
Make este un utilitar care permite automatizarea si eficientizarea sarcinilor. In mod particular este folosit pentru automatizarea compilarii programelor. Dupa cum s-a precizat, pentru obtinerea unui executabil provenind din mai multe surse este ineficienta compilarea de fiecara data a fiecarui fisier si apoi link-editarea. Se compileaza fiecare fisier separat, iar la o modificare se va recompila doar fisierul modificat.

Exemplu simplu Makefile
Utilitarul Make foloseşte un fişier de configurare denumit Makefile. Un astfel de fişier conţine reguli şi comenzi de automatizare. În continuare este prezentat un exemplu foarte simplu de Makefile cu ajutorul căruia se va specifica sintaxa Make.

'''Exemplu 7. Makefile'''

all: gcc -Wall intro-04.c -o intro-04 clean: rm -f intro-04

Pentru rularea exemplului de mai sus se folosesc comenzile:

$ make gcc -Wall intro-04.c -o intro-04 $ ./intro-04 variabila my_uber_var are valoarea 12345

Exemplul prezentat mai sus conţine două reguli: all şi clean. La rularea comenzii <tt>make</tt> se execută prima regulă din Makefile (în cazul de faţă all, nu contează în mod special denumirea). Comanda executată este <tt>gcc -Wall intro-04.c -o intro-04</tt>. Se poate preciza explicit ce regulă să se execute prin transmiterea ca argument comenzii <tt>make</tt>:

$ make clean rm -f intro-04 $ make all gcc -Wall intro-04.c -o intro-04

În exemplul de mai sus se foloseşte regula clean pentru a şterge executabilul intro-04 şi comanda make all pentru a obţine din nou acel executabil.

Se observă că nu se transmite niciun argument comenzii make pentru a preciza fisierul Makefile care va trebui analizat. În mod implicit, GNU Make caută, în ordine, fişierele GNUmakefile, Makefile, makefile şi le analizează. Pentru a preciza ce fişier Makefile trebuie analizat, se foloseşte opţiunea -f. Astfel, în exemplul de mai jos, folosim fişierul Makefile.ex1:

$ mv Makefile Makefile.ex1 $ make make: *** No targets specified and no makefile found. Stop. $ make -f Makefile.ex1 gcc -Wall intro-04.c -o intro-04 $ make -f Makefile.ex1 clean rm -f intro-04 $ make -f Makefile.ex1 all gcc -Wall intro-04.c -o intro-04

În primă fază se incearcă rularea simplă a comenzii <tt>make</tt>. Întrucât make nu găseşte niciunul din fişierele GNUmakefile, Makefile sau makefile, returnează eroare. Prin precizarea opţiunii <tt>-f Makefile.ex1</tt> se specifică fişierul Makefile de analizat. De asemenea, se poate preciza şi regula care să fie executată.

Sintaxa unei reguli
În continuare este prezentată sintaxa unei reguli dintr-un fişier Makefile:

target: prerequisites command

<tt>target</tt> este, de obicei, fişierul care se va obţine prin rularea comenzii <tt>command</tt>. După cum s-a observat şi din exemplul anterior, poate să fie o ţintă virtuală care nu are asociat un fişier. <tt>prerequisites</tt> reprezintă dependinţele necesare pentru a urmări regula; de obicei sunt fişiere necesare pentru obţinerea ţintei. reprezintă caracterul tab şi trebuie neaparat folosit înaintea precizării comenzii. <tt>command</tt> o listă de comenzi (niciuna*, una, oricâte) rulate în momentul în care se trece la obţinerea ţintei.

Un exemplu indicat pentru un fişier Makefile este:

'''Exemplu 8. Makefile.ex2'''

all: intro-04 intro-04: intro-04.o        gcc intro-04.o -o intro-04 intro-04.o: intro-04.c        gcc -Wall -c intro-04.c clean: rm -f *.o *~ intro-04

Se observă prezenţa regulii all care va fi executată implicit. all are ca dependinţă intro-04 şi nu execută nicio comandă; intro-04 are ca dependinţă intro-04.o şi realizează link-editarea fişierului intro-04.o; intro-04.o are ca dependinţă intro-04.c şi realizează compilarea şi asamblarea fişierului intro-04.c. Pentru obţinerea executabilului se foloseşte comanda:

$ make -f Makefile.ex2 gcc -Wall -c intro-04.c gcc intro-04.o -o intro-04

Funcţionarea unui fişier Makefile
Pentru explicarea funcţionării unui fişier Makefile, vom folosi exemplul de mai sus. În momentul rulării comenzii make se poate preciza target-ul care se doreşte a fi obţinut. Dacă acesta nu este precizat, este considerat implicit primul target întâlnit în fişierul Makefile folosit; de obicei, acesta se va numi all.

Pentru obţinerea unui target trebuie satisfăcute dependinţele (prerequisites) acestuia. Astfel,
 * pentru obţinerea target-ului all trebuie obţinut target-ul intro-04, care este un nume de executabil
 * pentru obţinerea target-ului intro-04 trebuie obţinut target-ul intro-04.o
 * pentru obţinerea target-ului intro-04.o trebuie obţinut intro-04.c; acest fişier există deja, şi cum acesta nu apare la randul lui ca target în Makefile, nu mai trebuie obţinut
 * drept urmare se rulează comanda asociată obţinerii intro-04.o; aceasta este <tt>gcc -Wall -c intro-04.c</tt>
 * rularea comenzii duce la obţinerea target-ului intro-04.o, care este folosit ca dependinţă pentru intro-04
 * se rulează comanda <tt>gcc intro-04.o -o intro-04</tt> pentru obţinerea intro-04
 * intro-04 este folosit ca dependinţă pentru all; acesta nu are asociată nicio comandă deci este automat obţinut.

De remarcat este faptul că un target nu trebuie să aibă neapărat numele fişierului care se obţine. Se recomandă, însă, acest lucru pentru înţelegerea mai uşoară a fişierului Makefile, şi pentru a beneficia de faptul că make utilizează timpul de modificare al fişierelor pentru a decide când nu trebuie să facă nimic.

Acest format al fişierului Makefile are avantajul eficientizării procesului de compilare. Astfel, după ce s-a obţinut executabilul intro-04 conform fişierului Makefile anterior, o nouă rulare a make nu va genera nimic:

$ make -f Makefile.ex2 make: Nothing to be done for `all'.

Mesajul "Nothing to be done for 'all'" înseamnă că ţinta all are toate dependinţele satisfăcute. Dacă, însă, folosim comanda touch pe fişierul obiect, se va considera că a fost modificat şi vor trebui refăcute target-urile care depindeau de el:

$ touch intro-04.o $ make -f Makefile.ex2 gcc intro-04.o -o intro-04 $ make -f Makefile.ex2 make: Nothing to be done for `all'.

La fel, dacă ştergem fişierul obiect, acesta va trebui regenerat, ca şi toate target-urile care depindeau, direct sau indirect, de el:

$ rm intro-04.o $ make -f Makefile.ex2 gcc -Wall -c intro-04.c gcc intro-04.o -o intro-04

Folosirea variabilelor
Un fişier Makefile permite folosirea de variabile. Astfel, un exemplu uzual de fişier Makefile este:

'''Exemplu 9. Makefile.ex3'''

CC = gcc CFLAGS = -Wall -g all: intro-04 intro-04: intro-04.o        $(CC) $^ -o $@ intro-04.o: intro-04.c        $(CC) $(CFLAGS) -c $< .PHONY: clean clean: rm -f *.o *~ intro-04

În exemplul de mai sus au fost definite variabilele CC şi CFLAGS. Variabila CC reprezintă compilatorul folosit, iar variabila CFLAGS reprezintă opţiunile (flag-urile) de compilare utilizate; în cazul de faţă sunt afişarea avertismentelor şi compilarea cu suport de depanare. Referirea unei variabile se realizează prin intermediul construcţiei $(VAR_NAME). Astfel, $(CC) se înlocuieşte cu gcc, iar $(CFLAGS) se înlocuieşte cu -Wall -g.

Nişte variabile predefinite sunt $@, $^ şi $<. $@ se expandează la numele target-ului. $^ se expandează la lista de cerinţe, iar $< se expandează la prima cerinţă. În acest fel, comanda $(CC) $^ -o $@ se expandează la gcc intro-04.o -o intro-04

iar comanda $(CC) $(CFLAGS) -c $< se expandează la gcc -Wall -g -c intro-04.c

Pentru mai multe detalii despre variabile consultaţi pagina info sau manualul online.

Folosirea regulilor implicite
De foarte multe ori nu este nevoie să se precizeze comanda care trebuie rulată; aceasta poate fi detectată implicit.

Astfel, în cazul în care se precizează regula:

main.o: main.c

se foloseşte implicit comanda $(CC) $(CFLAGS) -c -o $@ $<

Astfel, fişierul Makefile.ex2 de mai sus poate fi simplificat, folosind reguli implicite, ca mai jos:

'''Exemplu 10. Makefile.ex4'''

CC = gcc CFLAGS = -Wall -g all: intro-04 intro-04: intro-04.o intro-04.o: intro-04.c .PHONY: clean clean: rm -f *.o *~ intro-04

Pentru rulare, se foloseşte comanda:

$ make -f Makefile.ex4 gcc -Wall -g  -c -o intro-04.o intro-04.c gcc   intro-04.o   -o intro-04

Se observă că se folosesc reguli implicite. Makefile-ul poate fi simplificat şi mai mult, ca în exemplul de mai jos:

'''Exemplul 11. Makefile.ex5''' CC = gcc CFLAGS = -Wall -g all: intro-04 intro-04: intro-04.o .PHONY: clean

clean: rm -f *.o *~ intro-04

În exemplul de mai sus s-a eliminat regula <tt>intro-04.o: intro-04.c</tt>. Make "vede" că nu există fişierul intro-04.o şi caută fişierul C din care poate să-l obţină. Pentru aceasta creează o regulă implicită şi compilează fişierul intro-04.c:

$ make -f Makefile.ex5 gcc -Wall -g  -c -o intro-04.o intro-04.c gcc   intro-04.o   -o intro-04

De remarcat este faptul ca daca avem un singur fisier sursa nici nu trebuie sa existe un fisier Makefile pentru a obtine executabilul dorit. $ls intro-04.c $ make intro-04 cc intro-04.c -o intro-04

Pentru mai multe detalii despre reguli implicite consultaţi pagina info sau manualul online.

Exemplu complet de Makefile
Folosind toate facilitaţile de până acum, ne propunem compilarea unui executabil client şi a unui executabil server.

Fişierele folosite sunt:


 * executabilul server depinde de fişierele C server.c, sock.c, cli_handler.c, log.c, sock.h, cli_handler.h, log.h;
 * executabilul client depinde de fişierele C client.c, sock.c, user.c, log.c, sock.h, user.h, log.h;

Dorim, aşadar, obţinerea executabilelor <tt>client</tt> şi <tt>server</tt> pentru rularea celor două entităţi. Structura fişierului Makefile este prezentată mai jos:

'''Exemplu 12. Makefile.ex6''' CC = gcc                       # compilatorul folosit CFLAGS = -Wall -g              # optiunile pentru compilare LDLIBS = -lefence              # optiunile pentru linking all: client server client: client.o user.o sock.o log.o server: server.o cli_handler.o sock.o log.o client.o: client.c sock.h user.h log.h user.o: user.c user.h sock.o: sock.c sock.h server.o: server.c cli_handler.h sock.h log.h cli_handler.o: cli_handler.c cli_handler.h log.o: log.c log.h .PHONY: clean clean: rm -fr *~ *.o server client
 * 1) creeaza executabilele client si server
 * 1) leaga modulele client.o user.o sock.o in executabilul client
 * 1) leaga modulele server.o cli_handler.o sock.o in executabilul server
 * 1) compileaza fisierul client.c in modulul obiect client.o
 * 1) compileaza fisierul user.c in modulul obiect user.o
 * 1) compileaza fisierul sock.c in modulul obiect sock.o
 * 1) compileaza fisierul server.c in modulul obiect server.o
 * 1) compileaza fisierul cli_handler.c in modulul obiect cli_handler.o
 * 1) compileaza fisierul log.c in modulul obiect log.o

Pentru obţinerea executabilelor <tt>server</tt> şi <tt>client</tt> se foloseşte:

$ make -f Makefile.ex6 gcc -Wall -g -c -o client.o client.c gcc -Wall -g -c -o user.o user.c gcc -Wall -g -c -o sock.o sock.c gcc -Wall -g -c -o log.o log.c gcc client.o user.o sock.o log.o -lefence -o client gcc -Wall -g -c -o server.o server.c gcc -Wall -g -c -o cli_handler.o cli_handler.c gcc server.o cli_handler.o sock.o log.o -lefence -o server

Regulile implicite intră în vigoare şi se obţin, pe rând, fişierele obiect şi fişierele executabile. Variabila LDLIBS este folosită pentru a preciza bibliotecile cu care se face link-editarea pentru obţinerea executabilului.

Depanarea programelor
Existã câteva unelte GNU care pot fi folosite atunci când nu reuşim sã facem un program sã ne asculte. gdb, acronimul de la "Gnu DeBugger" este probabil cel mai util dintre ele, dar existã si altele, cum ar fi ElectricFence, gprof sau mtrace. gdb va fi prezentat pe scurt în secţiunile ce urmeazã.

GDB
Dacă doriţi să depanaţi un program cu GDB nu uitaţi să compilaţi programul cu optiunea -g. Folosirea acestei opţiuni duce la includerea în executabil a informaţiilor de debug.

Rularea GDB
GDB poate fi folosit în două moduri pentru a depana programul:


 * rulându-l folosind comanda gdb
 * folosind fisierul core generat în urma unei erori grave (de obicei segmentation fault)

Cea de a doua modalitate este utilă în cazul în care bug-ul nu a fost corectat înainte de lansarea programului. În acest caz, dacă utilizatorul întâlneşte o eroare gravă, poate trimite programatorului fişierul core cu care acesta poate depana programul şi corecta bug-ul.

Cea mai simplă formă de depanare cu ajutorul GDB este cea în care dorim să determinăm linia programului la care s-a produs eroarea. Pentru exemplificare considerăm următorul program:

'''Exemplu 13. exemplul-6.c'''

int f(int a, int b) { int c; 	c = a + b; 	return c; } int main { 	char *bug = 0; *bug = f(1, 2); return 0; }
 * 1) include <stdio.h>

Dupã compilarea programului acesta poate fi depanat folosind GDB. Dupã încãrcarea programului de depanat, GDB întrã în mod interactiv. Utilizatorul poate folosi apoi comenzi pentru a depana programul:

$ gcc -Wall -g exemplul-6.c $ gdb a.out [...] (gdb) run Starting program: /home/tavi/cursuri/so/lab/draft/intro/a.out Program received signal SIGSEGV, Segmentation fault. 0x08048411 in main at exemplul-6.c:16 16             *bug=f(1, 2); (gdb)

Prima comandă folosită este <tt>run</tt>. Această comandă va porni execuţia programului. Dacă această comandă primeşte argumente de la utilizator, acestea vor fi transmise programului. Înainte de a trece la prezentarea unor comenzi de bază din gdb, să demonstrăm cum se poate depana un program cu ajutorul fişierului core:

Segmentation fault (core dumped) Core was generated by `./a.out'. Program terminated with signal 11, Segmentation fault. 16             *bug=f(1, 2); (gdb)
 * 1) ulimit -c 4
 * 2) ./a.out
 * 1) gdb a.out core
 * 1) 0 0x08048411 in main  at exemplul-6.c:16

Comenzi de bază GDB
Câteva din comenzile de bază în gdb sunt <tt>breakpoint</tt>, <tt>next</tt> şi <tt>step</tt>. Prima dintre ele primeşte ca argument un nume de funcţie (ex: main), un număr de linie şi, eventual, un fişier (ex: <tt>break sursa.c:50</tt>) sau o adresă (ex: <tt>break *0x80483d3</tt>). Comanda next va continua executia programului până ce se va ajunge la următoarea linie din codul sursã. Dacă linia de executat conţine un apel de funcţie, funcţia se va executa complet. Dacă se doreşte şi inspectarea funcţiilor trebuie să se folosească <tt>step</tt>. Folosirea acestor comenzi este exemplificată mai jos:

$ gdb a.out (gdb) break main Breakpoint 1 at 0x80483f6: file exemplul-6.c, line 14. (gdb) run Starting program: /home/tavi/cursuri/so/lab/draft/intro/a.out Breakpoint 1, main at exemplul-6.c:14 14             char *bug=0; (gdb) next 16             *bug=f(1, 2); (gdb) next Program received signal SIGSEGV, Segmentation fault. 0x08048411 in main at exemplul-6.c:16 16             *bug=f(1, 2); (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/tavi/cursuri/so/lab/draft/intro/a.out Breakpoint 1, main at exemplul-6.c:14 14             char *bug=0; (gdb) next 16             *bug=f(1, 2); (gdb) step f (a=1, b=2) at exemplul-6.c:8 8              c=a+b; (gdb) next 9              return c; (gdb) next 10     } (gdb) next Program received signal SIGSEGV, Segmentation fault. 0x08048411 in main at exemplul-6.c:16 16             *bug=f(1, 2); (gdb)

O altă comandă utilă este list. Aceasta va lista fişierul sursă al programului depanat. Comanda primeşte ca argument un număr de linie (eventual nume fişier), o funcţie sau o adresă de la care să listeze. Al doilea argument este opţional şi precizează câte linii vor fi afişate. În cazul în care comanda nu are niciun parametru, ea va lista de unde s-a oprit ultima afişare.

$ gdb a.out (gdb) list exemplul-6.c:1 1      /* exemplul-6.c */ 2      #include &lt;stdio.h&gt;&gt; 3 4      int f(int a, int b) 5       { 6              int c; 7 8              c=a+b; 9              return c; 10      } (gdb) break exemplul-6.c:8 Breakpoint 1 at 0x80483d6: file exemplul-6.c, line 8. (gdb) run Starting program: /home/tavi/cursuri/so/lab/draft/intro/a.out Breakpoint 1, f (a=1, b=2) at exemplul-6.c:8 8              c=a+b; (gdb) next 9              return c; (gdb) continue Continuing. Program received signal SIGSEGV, Segmentation fault. 0x08048411 in main at exemplul-6.c:16 16             *bug=f(1, 2);

Comanda continue se foloseşte atunci când se doreşte continuarea execuţiei programului. Ultima comandă de bază este print. Cu ajutorul acesteia se pot afişa valorile variabilelor din funcţia curentă sau a variabilelor globale. <tt>print</tt> poate primi ca argument şi expresii complicate (dereferenţieri de pointeri, referenţieri ale variabilelor, expresii aritmetice, aproape orice expresie C validã). În plus, <tt>print</tt> poate afişa structuri de date precum <tt>struct</tt> şi <tt>union</tt>.

$ gdb a.out (gdb) break f Breakpoint 1 at 0x80483d6: file exemplul-6.c, line 8. (gdb) run Starting program: /home/tavi/cursuri/so/lab/draft/intro/a.out Breakpoint 1, f (a=1, b=2) at exemplul-6.c:8 8              c=a+b; (gdb) print a $1 = 1 (gdb) print b $2 = 2 (gdb) print c $3 = 1073792080 (gdb) next 9              return c; (gdb) print c $4 = 3 (gdb) finish Run till exit from #0 f (a=1, b=2) at exemplul-6.c:9 0x08048409 in main at exemplul-6.c:16 16             *bug=f(1, 2); Value returned is $5 = 3 (gdb) print bug $6 = 0x0 (gdb) print (struct sigaction)bug $13 = {__sigaction_handler = { 		sa_handler = 0x8049590 &lt;object.2&gt;, sa_sigaction = 0x8049590 &lt;object.2&gt; }, 	sa_mask = { 		__val = { 			3221223384, 1073992320, 1, 3221223428, 			3221223436, 134513290, 134513760, 0, 3221223384, 			1073992298, 0, 3221223436, 1075157952, 			1073827112, 1, 134513360, 0, 134513393, 134513648, 1, 			3221223428, 134513268, 134513760, 1073794080, 			3221223420, 1073828556, 1, 3221223760, 0, 			3221223804, 3221223846,	3221223866 		} 	}, 	sa_flags = -1073743402, sa_restorer = 0xbffff9f2} (gdb)

= Windows =

Compilatorul Microsoft cl.exe
Soluţia folosită pentru platforma Windows în cadrul acestui laborator este cl.exe, compilatorul Microsoft pentru C/C++. Recomandăm instalarea Microsoft Visual C++ Express 2008 (9.0) (versiunea Professional a Visual C++ este disponibilă gratuit în cadrul MSDNAA ). Programele C/C++ pot fi compilate prin intermediul interfeţei grafice sau în linie de comandă.

În Windows fişierele cod obiect au extensia .obj. Pentru a compila exemplul clasic "hello world" prezentat mai jos:

'''Exemplu 17. hello.c'''

int main(void) { 	printf("Hello, world!\r\n"); } se poate rula:
 * 1) include <stdio.h>

cl hello.c

Pentru lista de opţiuni ce pot fi pasate compilatorului, rulaţi în linia de comanda:

cl /?

Pentru lista de opţiuni ce pot fi transmise linker-ului, rulaţi în linia de comandă:

link /?

Aceste opţiuni pot apărea şi într-o invocare a lui cl, după opţiunea /link. După cum se poate vedea mai sus numele linker-ului este link (programul link.exe).

Conţinutul variabilei de sistem CL este adăugat automat de cl.exe în continuarea parametrilor luaţi din linia de comandă.

Se vor prezenta mai jos o serie de opţiuni uzuale:


 * /Wall - enable all warnings
 * /LIBPATH: - această opţiune indică linker-ului să caute şi în directorul dir bibliotecile pe care trebuie să le folosească programul; opţiunea se foloseşte după /link
 * /I - caută şi în acest director fişierele incluse prin directiva include
 * /c - se va face numai compilarea, adică se va omite etapa de link-editare.

Opţiunile cel mai des întâlnite privind optimizarea codului sunt afişate mai jos :

/O1 minimize space /O2 maximize speed /Os favor code space /Ot favor code speed /Od disable optimizations (default) /Og enable global optimization

Fişierele de ieşire pentru o sursă .c sunt :

/Fo name object file /Fa[file] name assembly listing file /Fp name precompiled header file /Fe name executable file

Biblioteci
La fel ca şi pe Linux, în Windows se pot crea biblioteci statice sau biblioteci partajate.

Crearea unei biblioteci statice
Vom considera exemplul folosit pentru crearea de biblioteci în Linux (intro-03-main.c, intro-03-util.h, intro-03-f1.c, intro-03-f2.c):

>cl /c intro-03-f1.c Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. intro-03-f1.c

>cl /c intro-03-f2.c Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. intro-03-f2.c

>cl /c intro-03-main.c Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. intro-03-main.c

>lib /out:intro.lib intro-03-f1.obj intro-03-f2.obj Microsoft (R) Library Manager Version 8.00.50727.42 Copyright (C) Microsoft Corporation. All rights reserved.

>cl intro-03-main.obj intro.lib Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. Microsoft (R) Incremental Linker Version 8.00.50727.42 Copyright (C) Microsoft Corporation. All rights reserved. /out:intro-03-main.exe intro-03-main.obj intro.lib

Pentru obţinerea unei biblioteci statice folosim comanda lib. Argumentul /out: precizează numele bibliotecii statice de ieşire. Biblioteca are de obicei extensia .lib. Pentru obţinerea executabilului se foloseşte cl care primeşte ca argumente fişierele obiect şi bibliotecile care conţin funcţiile dorite.

Crearea unei biblioteti partajate
Bibliotecile partajate din Linux au ca echivalent bibliotecile DLL (Dynamic Link Library) în Windows. Crearea unei bibilioteci partajate pe Windows este mai complicată decât pe Linux. Pe de o parte pentru că în afara bibliotecii partajate (<tt>dll</tt>), mai trebuie creată o bibliotecă de import (<tt>lib</tt>). Pe de altă parte, legarea bibliotecii partajate presupune exportarea explicită a simbolurilor (funcții, variabile) care vor fi folosite.

Pentru precizarea simobolurilor care vor fi exportate de bibliotecă se folosesc identificatori predefiniți:<tt>__declspec(dllimport)</tt> și <tt>__declspec(dllexport)</tt>. <tt>__declspec(dllimport)</tt> este folosit pentru a importa o funcţie dintr-o bibliotecă. La fel, <tt>__declspec(dllexport)</tt> este folosit pentru a exporta o funcție dintr-o bibliotecă. Exemplul de mai jos prezintă trei programe: două dintre ele vor fi legate într-o bibliotecă partajată, iar celălalt conține codul de utilizare a funcțiilor exportate.

Exemplu 3. intro-03-main.c

int main(void) { 	 f1; f2; return 0; }
 * 1) include <stdio.h>
 * 2) define DLL_IMPORTS
 * 3) include "intro-03-funs.h"

Exemplu 3. intro-03-funs.h

DLL_DECLSPEC void f1 (void); DLL_DECLSPEC void f2 (void);
 * 1) ifndef FUNS_H
 * 2) define FUNS_H  1
 * 1) ifdef DLL_IMPORTS
 * 2) define DLL_DECLSPEC __declspec(dllimport)
 * 3) else
 * 4) define DLL_DECLSPEC __declspec(dllexport)
 * 5) endif
 * 1) endif

Exemplu 3. intro-03-f1.c

void f1(void) { 	 printf("Fisierul curent este %s\n", __FILE__); }
 * 1) include <stdio.h>
 * 2) include "intro-03-funs.h"

Exemplu 3. intro-03-f2.c

void f2(void) { 	printf("Va aflati la linia %d din fisierul %s\n", __LINE__, __FILE__); }
 * 1) include <stdio.h>
 * 2) include "intro-03-funs.h"

Pentru crearea unei biblioteci partajate (<tt>dll</tt>) se foloseşte opţiunea <tt>/LD</tt> la comanda <tt>cl</tt>:

>cl /LD intro-03-f1.obj intro-03-f2.obj Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. Microsoft (R) Incremental Linker Version 8.00.50727.42 Copyright (C) Microsoft Corporation. All rights reserved. /out:intro-03-f1.dll /dll /implib:intro-03-f1.lib intro-03-f1.obj intro-03-f2.obj Creating library intro-03-f1.lib and object intro-03-f1.exp >cl main.obj intro-03-f1.lib Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. Microsoft (R) Incremental Linker Version 8.00.50727.42 Copyright (C) Microsoft Corporation. All rights reserved. /out:main.exe main.obj intro-03-f1.lib

Alternativ, biblioteca poate fi obținută cu ajutorul comenzii <tt>link</tt>:

>link /nologo /dll /out:intro-03.dll /implib:intro-03.lib intro-03-f1.obj intro-03-f2.obj Creating library intro-03.lib and object intro-03.exp >link /nologo /out:main.exe intro-03-main.obj intro-03.lib >main.exe Fisierul curent este intro-03-f1.c Va aflati la linia 6 din fisierul intro-03-f2.c

Nmake
Nmake este utilitarul folosit pentru compilare incrementală pe Windows. Nmake are o sintaxă foarte asemănătoare cu Make. Un exemplu simplu de makefile este cel ataşat parser-ului de la tema 1:

OBJ_LIST = parser.tab.obj parser.yy.obj CPPFLAGS = /nologo /W4 /Wp64 /EHsc CFLAGS  = /nologo /W4 /Wp64 /EHsc /Za EXE_NAMES = CUseParser.exe UseParser.exe DisplayStructure.exe all : $(EXE_NAMES) CUseParser.exe : CUseParser.obj $(OBJ_LIST) $(CPP) $(CPPFLAGS) /Fe$@ $** UseParser.exe : UseParser.obj $(OBJ_LIST) $(CPP) $(CPPFLAGS) /Fe$@ $** DisplayStructure.exe : DisplayStructure.obj $(OBJ_LIST) $(CPP) $(CPPFLAGS) /Fe$@ $** clean : exe_clean obj_clean obj_clean : del *.obj exe_clean : del $(EXE_NAMES)

= Exerciţii =

Quiz
Pentru auto-evaluare răspundeți la întrebările din acest quiz.

Exerciţii pre-laborator
Folosiţi [[media:lab1-pre.zip|arhiva de pre-sarcini]] a laboratorului.

Linux
Folosiţi directorul <tt>lin/</tt> din arhiva de pre-sarcini a laboratorului.


 * 1) Intraţi în subdirectorul <tt>ex1</tt>.
 * 2) * Folositi comanda make pentru a compila programul <tt>ex1.c</tt>
 * 3) * Cum se cheamă executabilul obţinut?
 * 4) * Rulaţi-l.
 * 5) Rămâneţi în subdirectorul <tt>ex1</tt>.
 * 6) * Curăţaţi directorul curent folosind <tt>make</tt> (trebuie să rămână doar fişierul Makefile şi <tt>ex1.c</tt>)
 * 7) * Parcurgeţi codul programului <tt>ex1.c</tt>. Ce observaţi?
 * 8) * Folosiţi o comandă simplă (<tt>gcc ...</tt>) pentru compilarea programului, dar care să includă opţiunea -Wall. Ce warning-uri apar?
 * 9) * Corectaţi greşelile din programul <tt>ex1.c</tt>?
 * 10) Modificati fisierul Makefile din directorul <tt>ex1</tt>.
 * 11) * În urma rulării comenzii <tt>make</tt> programul <tt>ex1.c</tt> trebuie să fie compilat cu opţiunea <tt>-Wall</tt> (la fel ca la punctul anterior).
 * 12) * Rulaţi executabilul astfel obţinut.
 * 13) Intraţi în directorul <tt>ex2</tt> şi modificaţi programul <tt>ex2.c</tt>.
 * 14) * Inlocuiti _doar_ comentariul <tt>/* TODO */</tt> astfel incat dupa compilare si rulare programul sa afiseze mesajul Hello, World!.
 * 15) * Pentru compilare folosiţi fişierul Makefile existent.

Windows
Folosiţi directorul <tt>win/</tt> din arhiva de pre-sarcini a laboratorului.


 * 1) Intraţi în subdirectorul <tt>ex1</tt>.
 * 2) * Folositi comanda nmake pentru a compila programul <tt>ex1.c</tt>
 * 3) * Cum se cheamă executabilul obţinut?
 * 4) * Rulaţi-l.
 * 5) Curăţaţi directorul curent folosind <tt>make</tt> (trebuie să rămână doar fişierul Makefile şi <tt>ex1.c</tt>)
 * 6) * Modificaţi programul <tt>ex1.c</tt> astfel încât să nu mai apară mesajele de avertizare de la punctul anterior.
 * 7) * Recompilaţi programul <tt>ex1.c</tt> şi rulaţi executabilul obţinut.
 * 8) Modificati fisierul Makefile din directorul <tt>ex1</tt>.
 * 9) * În urma rulării comenzii <tt>make</tt> programul <tt>ex1.c</tt> trebuie să fie compilat cu o opţiune de optimizare.
 * 10) * Rulaţi executabilul astfel obţinut.
 * 11) Intraţi în directorul <tt>ex2</tt> şi modificaţi programul <tt>ex2.c</tt>.
 * 12) * Inlocuiti _doar_ comentariul <tt>/* TODO */</tt> astfel incat dupa compilare si rulare programul sa afiseze mesajul Hello, World!.
 * 13) * Pentru compilare folosiţi fişierul Makefile existent.

Exerciţii de laborator
Folosiţi [[media:lab1-tasks.zip|arhiva de sarcini]] a laboratorului (alternativ: [[media:lab1-tasks.tar.gz|aici]]).

Linux
Folosiţi directorul <tt>lin/</tt> din arhiva de sarcini a laboratorului.
 * 1) Fazele compilării, creare makefile
 * 2) * (0.5 puncte) Intraţi în directorul <tt>comp/</tt>.
 * 3) ** Verificaţi conţinutul fişierului <tt>comp.c</tt> din directorul curent.
 * 4) ** Definiţi macrourile <tt>FIRST_NAME</tt> şi <tt>LAST_NAME</tt> cu prenumele respectiv numele vostru.
 * 5) ** Compilaţi programul manual şi obţineţi executabilul <tt>a.out</tt>. Rulaţi executabilul şi analizaţi rezultatul.
 * Hints
 * 1) *** Citiţi secţiunea Utilizare GCC din laborator.
 * 2) * (1 punct) Rămâneţi în directorul <tt>comp</tt>.
 * 3) ** Din fişierul sursă <tt>comp.c</tt> obţineţi fişierul preprocesat <tt>comp.i</tt> şi observaţi cum se modifică apelul funcţiei de afişare.De ce dimensiunea fişierului preprocesat este semnificativ mai mare decât a fişierului sursă iniţial?
 * 4) ** Din fişierul preprocesat <tt>comp.i</tt> obţineţi fişierul în limbaj de asamblare <tt>comp.s</tt>. Analizaţi conţinutul acestui fişier şi modificaţi-l astfel încât programul executabil rezultat să afişeze prima literă a numelui în loc de numele întreg.
 * 5) ** Din fişierul în limbaj de asamblare <tt>comp.s</tt> obţineti fişierul obiect <tt>comp.o</tt> iar din acesta generaţi fişierul executabil <tt>comp</tt>.
 * Hints
 * 1) *** Dacă iniţial programul afişa <tt> Harry Potter </tt>, în urma modificării va trebui sa afişeze <tt> Harry P </tt>.
 * 2) *** Folosiţi imaginea din secţiunea Utilizare GCC şi citiţi secţiunea Opţiuni.
 * 3) *(0.5 puncte) Curăţaţi directorul <tt>comp/</tt> de fişierele intermediare, păstraţi doar fişierul comp.c.
 * 4) ** Completaţi fişierul <tt>Makefile</tt> pentru automatizarea procesului de construire a tuturor fişierelor specificate mai sus (<tt>comp.i</tt>,<tt> comp.s</tt>, <tt>comp.o</tt>, <tt>comp</tt>).
 * 5) ** Prima ţintă din fişierul <tt>Makefile</tt> va trebui să construiască fişierul executabil <tt>comp</tt> trecând prin toate fazele compilării.
 * 6) ** Trebuie să existe ţinte pentru crearea fişierelor <tt>comp.i</tt>, <tt>comp.s</tt>, <tt>comp.o</tt>, astfel <tt> make comp.i </tt> va trebui să creeze fişierul preprocesat.
 * 7) ** Modificati fisierul <tt>comp.s</tt> astfel incat sa afiseze doar prima litera din nume.
 * 8) ** Rulati make si executabilul obtinut si analizati rezultatul.
 * Hints
 * 1) *** Folosiţi opţiunea -o pentru a specifica numele fişierului de ieşire din orice fază de compilare.
 * 2) *** Citiţi sectiunea GNU Make din laborator.
 * 3) Utilizare macrouri in linia de comandă, rulare make
 * 4) *(0.5 puncte ) Intraţi în directorul <tt>dbg/</tt>.
 * 5) ** Folosiţi <tt>make</tt> pentru compilare.
 * 6) ** Rulaţi executabilul obţinut.
 * 7) ** Modificaţi _doar_ fişierul <tt>Makefile</tt> pentru ca după compilare şi rulare să se afişeze mesajele transmise ca argument macroului <tt>Dprintf</tt>.
 * Hints:
 * 1) *** Trebuie definit macroul <tt>DEBUG__</tt>.
 * 2) *** Cum definiţi un macro din linia de comandă, cautaţi <tt>-D </tt> in gcc(1).
 * 3) *** Folosiţi variabila standard <tt>CPPFLAGS</tt>.
 * 4) *( 0.5 puncte) Rămâneţi în directorul <tt>dbg/</tt>.
 * 5) ** Creaţi două fişiere Makefile astfel:
 * 6) ** Fişierul <tt>Makefile.ndbg</tt> este folosit pentru compilarea fişierului <tt>debug_test.c</tt> fără suport de macrouri de debug.
 * 7) ** Fişierul <tt>Makefile.dbg</tt> este folosit pentru compilarea fişierului <tt>debug_test.c</tt> cu suport de macrouri de debug.
 * 8) ** Fişierul <tt>Makefile.ndbg</tt> duce la obţinerea executabilului <tt>ndbg</tt>.
 * 9) ** Fişierul <tt>Makefile.dbg</tt> duce la obţinerea executabilului <tt>dbg</tt>.
 * 10) * Folosiţi cele două fişiere Makefile pentru compilarea fişierului <tt>debug_test.c</tt>.
 * Hints:
 * 1) ** Folosiţi makefile-ul deja existent şi adaptaţi-l acolo unde este nevoie.
 * 2) ** Cum puteţi folosi un fişier Makefile specific folosind <tt>make</tt>.
 * 3) * În fiecare caz rulaţi executabilul obţinut.
 * 4) Utilizare biblioteci, flag-uri de compilare
 * 5) * (1 punct) Intraţi în directorul <tt>exp/</tt>.
 * 6) ** Verificaţi conţinutul fişierului <tt>exp.c</tt> din directorul curent.
 * 7) ** Rulaţi comanda <tt>make</tt>.
 * 8) ** Ce program generează eroarea respectivă?
 * 9) ** Modificaţi fişierul <tt>Makefile</tt> pentru a corecta eroarea.
 * 10) ** Rulaţi fişierul executabil şi analizaţi rezultatul. Este corect?
 * Hints
 * 1) *** Folosiţi variabila standard <tt>LDLIBS</tt>.
 * 2) *** Compilaţi programul cu toate avertismentele activate. Folosiţi variabila standard <tt> CFLAGS </tt>.
 * 3) *** Citiţi secţiunea Biblioteci din laborator.
 * 4) Crearea biblioteci, utilizare gdb
 * 5) * (2 puncte) Intraţi în directorul <tt>mean/</tt>.
 * 6) ** Completaţi fişierul <tt>Makefile</tt> astfel încât:
 * 7) *** La rularea comenzii <tt>make</tt> să creeze executabilele <tt>mean_a</tt> şi <tt>mean_so</tt>.
 * 8) *** Executabilul <tt>mean_a</tt> presupune obţinerea bibliotecii statice <tt>libmean_a.a</tt> şi legarea acesteia cu modulul obiect <tt>mean.o</tt>.
 * 9) *** Executabilul <tt>mean_so</tt> presupune obţinerea bibliotecii partajate <tt>libmean_so.so</tt> şi legarea acesteia cu modulul obiect <tt>mean.o</tt>.
 * 10) *** Bibliotecile sunt obţinute din modulele obiect <tt>am.o</tt> şi <tt>hm.o</tt>.
 * 11) ** Rulaţi executabilele astfel obţinute. Unde este problema?
 * 12) ** Folositi utilitarul <tt>gdb</tt> pentru a identifica eroarea si corectati-o.
 * Hints:
 * 1) *** Folositi optiunea -g pentru a obtine fisiere obiect ce contin simbolurile de debug, altfel gdb nu va e de folos.
 * 2) *** 1/2 = 0, 1.0/2 = 0.5
 * 3) *** Nu uitaţi să configuraţi variabila <tt>LD_LIBRARY_PATH</tt> pentru rularea executabilului <tt>mean_so</tt>.
 * 4) *** Pentru obţinerea bibliotecilor şi a executabilelor nu veţi putea folosi comenzi implicite; va trebui să scrieţi explicit comenzile.
 * 5) *** Folosiţi variabila standard <tt>LDFLAGS</tt>.
 * 6) *** Citiţi secţiunile Biblioteci şi Depanarea programelor din laborator.

Windows
Folosiţi directorul <tt>win/</tt> din arhiva de sarcini a laboratorului.


 * 1) Compilare din mai multe fişiere
 * 2) *(1 punct)Intraţi în directorul <tt>comp/</tt>.
 * 3) ** In fişierul <tt>main.c</tt> definiţi macrourile <tt>FIRST_NAME</tt> şi <tt>LAST_NAME</tt> cu prenumele respectiv numele vostru.
 * 4) ** Obţineţi executabilul <tt>main.exe</tt> prin compilarea şi legarea fişierelor C (<tt>main.c</tt> şi <tt>welcome.c</tt>).
 * 5) ** Rulaţi executabilul <tt>main.exe</tt>.
 * 6) ** Completaţi fişierul <tt>Makefile</tt> astfel încât la rularea comenzii <tt>nmake</tt> să se compileze fişierele C şi să se obţină executabilul <tt>main.exe</tt>
 * Hints:
 * 1) *** Citiţi secţiunea referitoare la Compilatorul Microsoft cl.exe din laborator.
 * 2) Utilizare macrouri in linia de comanda, rulare <tt>nmake</tt>
 * 3) * (2 puncte) Intraţi în directorul <tt>dbg/</tt>.
 * 4) ** Folosiţi <tt>nmake</tt> pentru compilare.
 * 5) ** Rulaţi executabilul obţinut.
 * 6) ** Modificaţi _doar_ fişierul <tt>Makefile</tt> pentru ca după compilare şi rulare să se afişeze mesajele transmise ca argument macroului <tt>Dprintf</tt>.
 * Hints:
 * 1) *** Trebuie definit macroul <tt>DEBUG__</tt>.
 * 2) *** Cum definiţi un macro din linia de comandă? (<tt>cl /?</tt>)
 * 3) *** Folosiţi variabila standard <tt>CFLAGS</tt>.
 * 4) *** Citiţi secţiunea Nmake din laborator.
 * 5) * Rămâneţi în directorul <tt>dbg/</tt>.
 * 6) ** Creaţi două fişiere Makefile astfel:
 * 7) *** Fişierul <tt>Makefile.ndbg</tt> este folosit pentru compilarea fişierului <tt>debug_test.c</tt> fără suport de macrouri de debug.
 * 8) *** Fişierul <tt>Makefile.dbg</tt> este folosit pentru compilarea fişierului <tt>debug_test.c</tt> cu suport de macrouri de debug.
 * 9) *** Fişierul <tt>Makefile.ndbg</tt> duce la obţinerea executabilului <tt>ndbg.exe</tt>.
 * 10) *** Fişierul <tt>Makefile.dbg</tt> duce la obţinerea executabilului <tt>dbg.exe</tt>.
 * 11) ** Folosiţi cele două fişiere Makefile pentru compilarea fişierului <tt>debug_test.c</tt>.
 * 12) ** În fiecare caz rulaţi executabilul obţinut.
 * Hints
 * 1) *** Folosiţi makefile-ul deja existent şi adaptaţi-l acolo unde este nevoie.
 * 2) Creare biblioteci
 * 3) * (2 puncte) Intraţi în directorul <tt>mean/</tt>.
 * 4) ** Completaţi fişierul Makefile astfel încât:
 * 5) *** La rularea comenzii <tt>make</tt> să creeze executabilele <tt>mean_static.exe</tt>, respectiv <tt>mean_dynamic.exe</tt>.
 * 6) *** Executabilul <tt>mean_static.exe</tt> presupune obţinerea bibliotecii statice <tt>mean_static.lib</tt> şi legarea acesteia cu modulul obiect <tt>mean.obj</tt>.
 * 7) *** Executabilul <tt>mean_dynamic.exe</tt> presupune obţinerea bibliotecii partajate <tt>mean_dynamic.dll</tt> şi legarea acesteia cu modulul obiect <tt>mean.obj</tt>.
 * 8) *** Bibliotecile sunt obţinute din modulele obiect <tt>am.obj</tt> şi <tt>hm.obj</tt>.
 * 9) ** Rulaţi executabilele astfel obţinute.
 * Hints:
 * 1) *** Pentru legare (atât pentru executabile cât şi pentru biblioteci) folosiţi comanda <tt>link</tt>.
 * 2) *** Definiti la compilare ( folosind /D ) macro-ul CONTEXT, astfel:
 * 3) **** CONTEXT=1, atunci cand se compileaza fisierele C pentru obtinerea bibliotecii statice.
 * 4) **** CONTEXT=2 sau 3 atunci cand se compileaza fisierele C pentru obtinerea bibliotecii dinamice in functie de rolul pe care il are fisierul in crearea bibliotecii ( 2-importa functii din biblioteca sau 3-exporta functii din bibilioteca).
 * 5) *** Atentie la numele fisierelor obiect.
 * 6) *** Citiţi secţiunea Biblioteci din laborator.

= Soluţii =


 * [[Media:lab1-tasks-sol.zip|Soluţii exerciţii laborator 1]]

= Resurse utile =


 * 1) GCC Online Documentation
 * 2) The C Preprocessor
 * 3) GNU C Library
 * 4) Program Library HOWTO
 * 5) Using LD, the GNU Linker
 * 6) GNU Make Manual
 * 7) GDB Documentation
 * 8) Visual C++ Express
 * 9) Nmake Tool
 * 10) Building and Linking with Libraries
 * 11) Dynamic Link Library
 * 12) Creating and Using DLLs
 * 13) Dynamic Libraries

= Note =


 * 1) info make "Using Variables"
 * 2) http://www.gnu.org/software/make/manual/make.html#Using-Variables
 * 3) info make "Implicit Rules"
 * 4) http://www.gnu.org/software/make/manual/make.html#Implicit-Rules
 * 5) http://www.microsoft.com/express/vc/
 * 6) Nmake Reference
 * 7) Programul MSDNAA