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 prezenta un anumit set de concepte și va conține următoarele activități:
prezentare teoretică
parcurgerea exercițiilor rezolvate
rezolvarea exercițiilor propuse
Pentru o desfășurare cât mai bună a laboratorului și o înțelegere deplină a conceptelor vă recomandăm să parcurgeți conținutul laboratorului de acasă. De asemenea, pentru consolidarea cunoștințelor folosiți suportul de laborator prezentat în paragraful următor.
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 7.
În cadrul acestui laborator introductiv va fi prezentat mediul de lucru care va fi folosit în cadrul laboratorului de Sisteme de Operare cât și în rezolvarea temelor de casă.
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.
GCC este suita de compilatoare implicită pe majoritatea distribuțiilor Linux. Pentru mai multe detalii despre proiectul GCC apăsați pe butonul Show (de acum înainte secțiunile suplimentare vor fi ascunse folosind astfel de butoane).
GCC este unul dintre primele pachete software dezvoltate de organizația „Free Software Fundation” în cadrul proiectului GNU (Gnu's Not Unix). Proiectul GNU a fost inițiat de Richard Stallman ca un protest împotriva software-ului proprietar la începutul anilor '80.
La început, GCC se traducea prin “GNU C Compiler”, pentru că 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 compilatorul cel mai portat.
În cadrul laboratoarelor de Sisteme de Operare ne vom concentra asupra facilităților oferite de compilator pentru limbajele C și C++. GCC are suport pentru stadardele ANSI, ISO C, ISO C99, POSIX, dar și multe extensii folositoare care nu sunt incluse în niciunul din standarde; unele dintre aceste extensii vor fi prezentate în secțiunile ce urmează.
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ă, în cazul nostru hello.c.
so@spook$ ls
hello.c
so@spook$ gcc hello.c
so@spook$ ls
a.out hello.c
so@spook$ ./a.out
SO, ... hello world!
so@spook$ ls
hello.c
so@spook$ gcc hello.c -o hello
so@spook$ ls
hello hello.c
so@spook$ ./hello
SO, ... hello world!
Așadar, comanda gcc hello.c a fost folosită pentru compilarea fișierului sursă hello.c. Rezultatul a fost obținerea fișierului executabil a.out (nume implicit utilizat de gcc). Dacă se dorește obținerea unui executabil cu un alt nume se poate folosi opțiunea -o.
În mod similar se poate folosi g++ pentru compilarea unui program sursă C++.
Compilarea se referă la obținerea unui fișier executabil dintr-un fișier sursă. După cum am văzut în paragraful anterior comanda gcc a dus la obținerea fişierului executabil hello din fişierul sursă hello.c. Intern, gcc trece prin mai multe faze de prelucrare a fişierului sursă până la obținerea executabilului. Aceste faze sunt evidențiate în diagrama de mai jos:
Implicit, la o invocare a comenzii gcc/g++ se obţine din fişierul sursă un executabil. Folosind diverse opțiuni, putem opri compilarea la una din fazele intermediare astfel:
-E - se realizează doar preprocesarea fişierului sursă
gcc -E hello.c – va genera fişierul preprocesat pe care, implicit, îl va afişa la ieşirea standard.
-S - se realizează inclusiv faza de compilare
gcc -S hello.c – va genera fişierul în limbaj de asamblare hello.s
-c - se realizează inclusiv faza de asamblare
gcc -c hello.c – va genera fişierul obiect hello.o
Opţiunile de mai sus pot fi combinate cu -o pentru a specifica fişierul de ieşire.
Preprocesarea presupune înlocuirea directivelor de preprocesare din fişierul sursă C. Directivele de preprocesare încep cu #. Printre cele mai folosite sunt:
#include – pentru includerea fişierelor header într-un alt fișier.
#define și #undef – pentru definirea, respectiv anularea definirii de macrouri.
#if, #ifdef, #ifndef, #else, #elif, #endif, pentru compilarea condiţionată.
utile pentru comentarea bucăților mari de cod. Pentru a comenta toată funcția do_evil_things de mai jos nu putem folosi comentarii de tip C, ca în exemplul din dreapta, întrucat limbajul C nu permite comentariile imbricate. În astfel de cazuri se poate folosi directiva #if <condiţie> ca în exemplul din stânga.
#if 0 int do_evil_things(context_t *ctx){int go_drink;/* set student mode ON :) */
ctx->go_drink = NO;}#endif
/*
int do_evil_things(context_t *ctx)
{
int go_drink;
/* set student mode ON :) */
ctx->go_drink = NO;}*/
utile pentru evitarea includerii de mai multe ori a unui fişier header, tehnică numită include guard. Astfel, în exemplul de mai jos, dacă fişierul <string.h> este inclus, simbolul _STRING_H este deja definit de la prima includere, iar a doua operaţie de includere nu va avea niciun efect.
#ifndef _STRING_H#define _STRING_H 1
__BEGIN_DECLS
/* Get size_t and NULL from <stddef.h>. */#define __need_size_t#define __need_NULL/*
* string related defines
*/#endif /* string.h */
__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.
#include <stdio.h>#define show_var(a) printf("Variable %s has value %d\n", #a, a);int main(void){int teh_var =42;
show_var(teh_var);return0;}
so@spook$ gcc-o show show.c
so@spook$ ls
show show.c
so@spook$ ./show
Variable teh_var has value 42
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.
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:
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:
#ifdef DEBUG#define Dprintf(msg) printf(msg)#else#define Dprintf(msg) /* do nothing */#endif
Definiţia aceasta nu permite apelul lui Dprintf cu mai multe argumente şi, implicit, nici afişarea formatată. O soluţie este dată de implementarea prin intermediul macro-urilor cu număr variabil de parametri sau variadic macros:
#ifdef DEBUG#define Dprintf(msg,...) printf(msg, __VA_ARGS__)#else#define Dprintf(msg,...) /* do nothing */#endif
Singura problemă care poate apărea este folosirea Dprintf exact cu un argument. În acest caz macroul se expandează la Dprintf(msg,) – expresie nevalidă în C (din cauza virgulei de la sfârșit). 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):
#ifdef DEBUG#define Dprintf(msg,...) printf(msg, ##__VA_ARGS__)#else#define Dprintf(msg,...) /* do nothing */#endif
Un ultim retuş este afişarea, dacă se doreşte, a fişierului şi liniei unde s-a apelat macroul:
Asamblarea este faza în care codul scris în limbaj de asamblare este tradus în cod mașină reprezentând codificarea binară a instrucțiunilor programului iniţial. Fişierul obţinut poartă numele de fişier cod obiect, se obţine folosind opţiunea -c a compilatorului şi are extensia .o.
so@spook$ ls
hello.c
so@spook$ gcc-c hello.c
so@spook$ ls
hello.c hello.o
Pentru obținerea unui fişier executabil este necesară rezolvarea diverselor simboluri prezente în fişierul obiect. Această operaţie poartă denumirea de editare de legături, link-editare, linking sau legare.
void f(void);/*
* no definition for f here
*/int main(void){
f();return0;}
so@spook$ ls
sample.c
so@spook$ gcc-c-o sample sample.c
so@spook$ ls
sample.c sample.o
so@spook$ gcc-o sample sample.c
/tmp/ccOVreJg.o: In function`main':
sample.c:(.text+0x7): undefined reference to `f'
collect2: ld returned 1exit status
so@spook$ ls
sample.c
so@spook$ gcc-c-o sample sample.c
so@spook$ ls
sample.c sample.o
so@spook$ gcc-o sample sample.c
so@spook$ ls
sample sample.c sample.o
Observăm că în partea stângă deși am obținut fișierul obiect sample.o, linkerul nu poate genera fişierul executabil întrucât nu găseşte definiţia funcţiei f. În partea dreaptă totul decurge normal, definiţia funcţiei f fiind inclusă în fişierul sursă.
În mod implicit, o rulare a gcc oferă puține avertismente utilizatorului. Pentru a activa afișarea de avertismente se folosesc opțiunile de tip -W cu sintaxa -Woptiune-avertisment. optiune-avertisment poate lua mai multe valori posibile printre care return-type, switch, unused-variable, uninitialized, implicit, all. Folosirea opțiunii -Wall înseamnă afișarea tuturor avertismentelor care pot cauza inconsistențe la rulare.
Considerăm ca fiind indispensabilă folosirea opțiunii -Wall 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:
#include <stdio.h>int main(void){int min =10, max =20, midpoint;/* midpoint = min+(max-min)/2; */
midpoint = min +(max - min)>>1;printf("The middle of interval
[%d, %d] is %d\n",
min, max, midpoint);return0;}
so@spook$ ls
middle.c
so@spook$ gcc-o middle middle.c
so@spook$ ./middle
Middle of interval [10, 20] is 10
so@spook$ gcc-Wall-o middle middle.c
middle.c: In function ‘main’:
middle.c:8: warning: suggest parentheses around ‘+’ inside ‘>>’
La prima rulare, rezultatul nu e nici pe departe cel așteptat. Eroarea poate fi detectată ușor dacă includem și opțiunea -Wall la compilare. (operatorul + are prioritate în fața operatorului >>)
-Lcale – instruiește compilatorul să caute și în directorul cale bibliotecile pe care le folosește 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 trebuie să se numească 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-optimizări, 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 pune accentul pe optimizările pentru care duc la reducerea dimensiunii codului generat, si nu a vitezei la execuție.
-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.
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ă:
#include <stdio.h>#include "util.h"void f2(void){printf("Current line %d in file %s\n",
__LINE__, __FILE__);}
În programul de mai sus se apelează funcțiile f1 și f2 în funcția main pentru a afișa diverse informații. Pentru compilarea acestora se transmit toate fișierele C ca argumente către gcc:
so@spook$ ls
f1.c f2.c main.c util.h
so@spook$ gcc-Wall main.c f1.c f2.c -o main
so@spook$ ls
f1.c f2.c main main.c util.h
so@spook$ ./main
Current file name f1.c
Current line 8infile f2.c
Executabilul a fost denumit main; pentru acest lucru s-a folosit opțiunea -o.
Se observă folosirea fișierului header 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 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 f1.c și f2.c. Codul acestora este integrat în executabil în momentul link-editării.
În general, pentru obținerea unui executabil din surse multiple se obișnuiește compilarea fiecărei surse până la modul obiect și apoi link-editarea acestora:
so@spook$ ls
f1.c f2.c main.c util.h
so@spook$ gcc-Wall-c f1.c
so@spook$ gcc-Wall-c f2.c
so@spook$ gcc-Wall-c main.c
so@spook$ ls
f1.c f1.o f2.c f2.o main.c main.o util.h
so@spook$ gcc-o main main.o f1.o f2.o
so@spook$ ls
f1.c f1.o f2.c f2.o main main.c main.o util.h
so@spook$ ./main
Current file name f1.c
Current line 8infile f2.c
Se observă obținerea executabilului main prin legarea modulelor obiect. Această abordare are avantajul eficienței. Dacă se modifică fișierul sursă f2.c 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.
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:
Biblioteca matematică este denumită libm.a sau libm.so. În Linux bibliotecile sunt de două tipuri:
statice - au, de obicei, extensia .a
partajate - 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ă:
Pentru crearea de biblioteci vom folosi fișierele din secțiunea Compilarea din mai multe fișiere. Vom include modulele obiect rezultate din fișierele sursă f1.c și 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:
O bibliotecă statică este o arhivă ce conține fișiere obiect creată cu ajutorul utilitarului ar ( interpretați parametrii rc).
so@spook$ ar rc libintro.a f1.o f2.o
so@spook$ gcc-Wall main.c -o main -lintro/usr/bin/ld: cannot find-lintro
collect2: ld returned 1exit status
so@spook$ gcc-Wall main.c -o main -lintro -L.
so@spook$ ./main
Current file name is f1.c
Current line 5infile f2.c
Atenție: -lintro trebuie să apară după specificarea sursei și a fișierului executabil
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).
Dacă biblioteca se numește libnume.a, atunci ea va fi referită cu -lnume
Spre deosebire de o bibliotecă statică despre care am văzut că nu este nimic altceva decât o arhivă de fișiere obiect, o bibliotecă partajată este ea însăși un fișier obiect. Crearea unei biblioteci partajate se realizează prin intermediul linker-ului. Optiunea -shared indică compilatorului să creeze un obiect partajat și nu un fișier executabil. Este, de asemenea, indicată folosirea opțiunii -fPIC la crearea fișierelor obiect.
so@spook$ gcc-fPIC-c f1.c
so@spook$ gcc-fPIC-c f2.c
so@spook$ gcc-shared f1.o f2.o -o libintro_shared.so
so@spook$ gcc-Wall main.c -o main -lintro_shared -L.
so@spook$ ./main
./main: 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ă.
Î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:
so@spook$ exportLD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
so@spook$ ./main
Current file name is f1.c
Current line 5infile 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:
so@spook$ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. ./main
Fisierul curent este f1.c
Va aflati la linia 5 din fisierul f2.c
so@spook$ ./main
./main: error while loading shared libraries: libintro_shared.so:
cannot open shared object file: No such file or directory
Make este un utilitar care permite automatizarea și eficientizarea sarcinilor. În mod particular este folosit pentru automatizarea compilării programelor. După cum s-a precizat, pentru obținerea unui executabil provenind din mai multe surse este ineficientă compilarea de fiecare dată a fiecărui fișier și apoi link-editarea. Se compilează fiecare fișier separat, iar la o modificare se va recompila doar fișierul modificat.
so@spook$ make clean
rm-f hello
so@spook$ make all
gcc-Wall hello.c -o hello
Exemplul prezentat mai sus conține două reguli: all și clean. La rularea comenzii make se execută prima regulă din Makefile (în cazul de față all, nu contează în mod special denumirea). Comanda executată este gcc -Wall hello.c -o hello. Se poate preciza explicit ce regulă să se execute prin transmiterea ca argument comenzii make (comanda make clean pentru a șterge executabilul hello și comanda make all pentru a obține din nou acel executabil).
Î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:
so@spook$ mv Makefile Makefile.ex1
so@spook$ makemake: *** No targets specified and no makefile found. Stop.
so@spook$ make-f Makefile.ex1
gcc-Wall hello.c -o hello
so@spook$ make-f Makefile.ex1 clean
rm-f hello
În continuare este prezentată sintaxa unei reguli dintr-un fișier Makefile:
target - este, de obicei, fișierul care se va obține prin rularea comenzii command. După cum s-a observat și din exemplul anterior, poate să fie o țintă virtuală care nu are asociat un fișier.
prerequisites - reprezintă dependențele necesare pentru a urmări regula; de obicei sunt fișiere necesare pentru obținerea țintei.
<tab> - reprezintă caracterul tab și trebuie neaparat folosit înaintea precizării comenzii.
command - 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:
Pentru obținerea unui target trebuie satisfăcute dependențele (prerequisites) acestuia. Astfel, pentru obținerea targetului implicit (primul target), în cazul nostru all:
pentru obținerea target-ului all trebuie obținut target-ul hello, care este un nume de executabil
pentru obținerea target-ului hello trebuie obținut target-ul hello.o
pentru obținerea target-ului hello.o trebuie obținut hello.c; acest fișier există deja, și cum acesta nu apare la rândul lui ca target în Makefile, nu mai trebuie obținut
drept urmare se rulează comanda asociată obținerii hello.o; aceasta este gcc -Wall -c hello.c
rularea comenzii duce la obținerea target-ului hello.o, care este folosit ca dependență pentru hello
se rulează comanda gcc hello.o -o hello pentru obținerea executabilului hello
hello este folosit ca dependență pentru all; acesta nu are asociată nici o 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 hello conform fișierului Makefile anterior, o nouă rulare a make nu va genera nimic:
so@spook$ make-f Makefile.ex2
make: Nothing to be donefor'all'.
Î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.
Variabile predefinite folositoare sunt:
$@ se expandează la numele target-ului.
$^ se expandează la lista de cerințe.
$< se expandează la prima cerință.
Pentru mai multe detalii despre variabile consultați pagina info [1] sau manualul online [2]
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.
Există câteva unelte GNU care pot fi folosite atunci când un program se comportă anormal. gdb, acronimul de la “Gnu DeBugger” este probabil cel mai util dintre ele, dar există și altele, cum ar fi ElectricFence, gprof sau mtrace. gdb este prezentat pe scurt aici.
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 2010 (10.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 cele ce urmează vom prezenta compilarea folosind linia de comandă. În Windows fișierele cod obiect au extensia *.obj.
/LIBPATH:<dir> - 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<dir> - 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.
/D<define_symbol> - definirea unui macro de la compilare
Opțiuni privind optimizarea codului:
/O1 minimizează spațiul ocupat
/O2 maximizează viteza
/Os favorizează spațiul ocupat
/Ot favorizează viteza
/Od fără optimizări (implicit)
/Og activează optimizările globale
Setarea numelui pentru diferite fișiere de ieșire:
/Fo<file> nume fișier obiect
/Fa<file> nume fișier în cod de asamblare
/Fp<file> nume fișier header precompilat
/Fe<file> nume fișier executabil
Exemple:
Creare fișier obiect myobj.obj din sursa mysrc.c:
cl /Fomyobj.obj /c mysrc.c
Creare fișier myasm.asm în cod de asamblare din sursa mysrc.c:
Pentru a crea biblioteci statice se folosește comanda lib
>lib /out:<nume.lib><lista fișiere obiecte>
Vom considera exemplul folosit pentru crearea de biblioteci în Linux (main.c, util.h, f1.c, f2.c):
# obținem fișierul obiect f1.obj din sursa f1.c>cl /c f1.c
Microsoft (R)32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
f1.c
#obținem fișierul f2.obj din sursa f2.c>cl /c f2.c
Microsoft (R)32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
f2.c
>cl /c main.c
Microsoft (R)32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
main.c
#obținem biblioteca statică intro.lib din f1.obj și f2.obj>lib /out:intro.lib f1.obj f2.obj
Microsoft (R) Library Manager Version 8.00.50727.42
Copyright (C) Microsoft Corporation. All rights reserved.
#intro.lib este compilat împreună cu main.obj pentru a obține main.exe>cl 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:main.exe
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.
Bibliotecile partajate din Linux au ca echivalent bibliotecile DLL (Dynamic Link Library) în Windows. Crearea unei biblioteci partajate pe Windows este mai complicată decât pe Linux. Pe de o parte, pentru că în afara bibliotecii partajate (dll), mai trebuie creată o bibliotecă de import (lib). Pe de altă parte, legarea bibliotecii partajate presupune exportarea explicită a simbolurilor (funcții, variabile) care vor fi folosite.
Pentru precizarea simbolurilor care vor fi exportate de bibliotecă se folosesc identificatori predefiniți:
__declspec(dllimport), este folosit pentru a importa o funcție dintr-o bibliotecă.
__declspec(dllexport), 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.
#include <stdio.h>#include "funs.h"void f2(void){printf("Current line %d in file %s\n",
__LINE__, __FILE__);}
Așadar, pentru crearea bibliotecii partajate și utlizarea acesteia de către programul main parcurgem următorii pași:
f1.c va exporta funcția f1() folosind __declspec(dllexport)
f2.c va exporta funcția f2() folosind __declspec(dllexport)
main.c va importa funcțiile f1() și f2() folosind __declspec(dllimport)
după obținerea fișierelor obiect f1.obj și f2.obj acestea vor fi folosite la crearea bibliotecii partajate folosind opțiunea /LD a comenzii cl.
în final legăm main.obj cu biblioteca partajată și obținem main.exe
>cl /LD f1.obj 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:f1.dll
/dll
/implib:f1.lib
f1.obj
f2.obj
Creating library f1.lib and object f1.exp
>cl main.obj 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
f1.lib
Alternativ, biblioteca poate fi obținută cu ajutorul comenzii link:
>link/nologo /dll /out:intro.dll /implib:intro.lib f1.obj f2.obj
Creating library intro.lib and object intro.exp
>link/nologo /out:main.exe main.obj intro.lib
>main.exe
Current file name is f1.c
Current line 6infile f2.c
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:
La rularea nmake bounds_static.lib să se creeze biblioteca statică bounds_static.lib. Biblioteca va conține fișierele obiect asociate fișierelor min.c și max.c
La rularea comenzii nmake să se creeze fișierul executabil bounds_static.exe obținut din legarea fișierului obiect corespunzător fișierului bounds.c cu biblioteca bounds_static.lib
Chemați asistentul înainte de a trece la subpunctul următor.
Creare bibliotecă dinamică
Rămâneți în directorul win/3-bounds/
Completați fișierul Makefile.dynamic astfel încât:
La rularea nmake bounds_dynamic.dll să se creeze biblioteca dinamică bounds_dynamic.dll. Biblioteca va conține fișierele obiect asociate fișierelor min.c și max.c
La rularea comenzii nmake să se creeze fișierul executabil bounds_dynamic.exe obținut prin legarea fișierului obiect corespunzător fișierului bounds.c cu biblioteca bounds_dynamic.dll
La rularea comenzii make libhexdump_static să creeze biblioteca statică libhexdump_static.a Biblioteca va conține fișierele obiect asociate fișierelor hexdump.c și sample.c
La rularea comenzii make să creeze executabilul main_static obținut din legarea fișierului obiect corespunzător lui main.c cu biblioteca libhexdump_static.a.
Completați fișierul Makefile_dynamic reguli astfel încât:
La rularea comenzii make libhexdump_dynamic să creeze biblioteca dinamică libhexdump_dynamic.so. Biblioteca va conține fișierele obiect asociate fișierelor hexdump.c și sample.c
La rularea comenzii make pe lângă biblioteca dinamică libhexdump_dynamic.so obținută anterior să se creeze și executabilul main_dynamic obținut din legarea fișierului obiect corespunzător lui main.c cu biblioteca partajată libhexdump_dynamic.so.