Lucrarea 3 Programare în C sub Linux 1. Introducere C este un limbaj de programare de nivel înalt, care exist? înc? din primele zile ale existen?ei sistemelor UNIX. Limbajul C a fost creat de Dennis Ritchie la Bell Laboratoires pentru a ajuta la dezvoltarea sistemului de operare UNIX. Printre motivele care au facut din C unul din cele mai folosite limbaje de programare la ora actual?: - este un limbaj foarte portabil. Aproape toate calculatoarele ?i sistemele de operare dispun de un compilator de C, iar sintaxa limbajului ?i biliotecile de func?ii sunt standardizate. - programele scrise în C sunt rapide - C este limbajul de sistem pentru toate versiunile de UNIX. Pentru a veni în întâmpinarea noilor cerin?e în materie de dezvoltare de programe, exist? extenii ale limbajului C, ca de exemplu C++ - limbaj de programare orientat pe obiecte. Compilatorul de C care este disponibil pentru Linux se nume?te GNU C sau GCC. Compilatorul este creat sub licen?a Free Software Foundation ?i poate fi distribuit în mod liber. 2. Desf??urarea lucr?rii 2.1. Compilatorul GNU C. Compilatorul GNU C care este distribuit cu sistemul Linux este un compilator complet opera?ional, compatibil ANSI C. Aceast? lucrare presupune ca sunte?i familiari cu limbajul C. 2.1.1. Apelarea compilatorului. Compilatorul este invocat în felul urm?tor: gcc [op?iuni] [nume_fi?iere] Opera?iile specificate de op?iuni vor fi executate asupra fiec?rui fi?ier care este specificat în linia de comand?. În continuare sunt descrise op?iunile care sunt cel mai des utilizate. 2.1.2. Op?iuni GCC Exist? mai mult de 100 de op?iuni ale compilatorului, multe dintre ele probabil c? nu le ve?i folosi niciodat?. O documenta?ie complet? poate fi ob?inut? cu "info gcc". Pentru a înv??a cum s? folosi?i texinfo, apela?i "info info". Multe din op?iunile GCC sunt formate din mai mult de un caracter, de aceea este necesar ca fiecare op?iune s? înceap? cu "-" (nu se pot grupa mai multe op?iuni cu acela?i "-"). Dac? compila?i un program folosind GCC f?r? nici o op?iune, va creea un fi?ier executabil cu numele a.out. Exemplu: gcc test.c Pentru a specifica un alt nume pentru fi?ierul executabil, folosi?i op?iunea "-o nume_executabil": gcc -o test test.c Op?iunea "-c": genereaz? doar cod obiect (extensia .o), f?r? asamblare ?i editare de leg?turi. Op?iuni pentru depanare ?i monitorizare a timpului de execu?ie: "-g": GCC va genera informa?ii folosite la depanare (cu depanatorul gdb) "-pg": GCC va adauga cod suplimentar care, atunci când este executat, generaz? informa?ii care pot fi folosite de gprof pentru a afla detalii cu privire la viteza de execu?ie a programului. "-Wall": activeaza diverse warning-uri compilatorului Exemplu: int prod(int a, int b) { return a * b; } int main(int argc, char **argv) { printf("\n%s",argv[0]); int i, j, p; p = 1; for (j = 0; j < 10000000; ++j) // 7 zero-uri for (i = 0; i < 4; ++i) p = prod(p, i + j); printf("%d\n",p); return p; } 2.1.1 Biblioteci Bibliotecile de functii (en: libraries) au aparut pentru a oferi o mai mare flexibilitate dezvoltatorilor de software. Acestea reunesc functii des utilizate de catre mai multe programe, fara a fi necesar astfel rescrierea acestora pentru fiecare program in parte. Trebuie sa amintim intai cum sunt utilizate aceste biblioteci de functii. Dupa cum se stie, programele sursa trec prin faza de compilare pentru a ajunge programe executabile. De cele mai multe ori, prin „compilare” se inteleg implicit de fapt doua procese: 1. compilare = trecerea din limbaj de programare in cod obiect 2. link-editare = editarea legaturilor cu bibliotecile de functii La apelul unei comenzi precum $ gcc -Wall -o GCC-ul apeleaza implicit si linker-ul – ld. Pentru a inhiba acest comportament, se poate utiliza parametrul -c, in acest fel obtinandu-se doar un simplu fisier obiect. Putem folosi aceasta facilitate pentru a compila functiile unei aplicatii in fisiere obiect separate. Ulterior, putem compila aplicatia mare, incluzand fisierele obiect compilate anterior. Exemplu: In a21.c: #include int prod(int a, int b) { printf("%d * %d = %d\n", a, b, a * b); return a * b; } in a22.c: #include int prod(int a, int b); int main() { int a = 4; int b = 3; int p; printf("a is %d, b is %d\n", a, b); p = prod(a, b); printf("p is %d\n", p); return p; } $ gcc a22.c -o a2 ? eroare de linkare Corect este: $ gcc -c a21.c $ gcc -c a22.c $ gcc a21.o a22.o -o a2 $ ./a2 2.2. Depanarea programelor folosind gdb gdb este un depanator puternic, care poate fi folosit pentru depanarea programelor C ?i C++. El permite inspectarea structurii interne a memoriei care este utilizat? de program în timpul execu?iei. Câteva din func?iile gdb: - permite monitorizarea valorilor variabilelor programului. - permite setarea punctelor de întrerupere (breakpoints) care vor opri programul la o linie de cod specificat?. - permite execu?ia pas cu pas a programului. 2.2.1. Apelarea gdb gdb se apeleaz? cu comanda "gdb [nume_executabil]". Pentru a afla mai multe despre gdb da?i "man gdb" sau "info gdb". Pentru a folosi corect depanatorul, nu uita?i s? compila?i programul cu op?iunea "-g". 2.2.2. Comenzi de baz? file nume_executabil - Încarc? programul executabil care urmeaz? a fi depanat kill - termin? programul care este depanat list - listeaz? sec?iunile din codul surs? care au fost folosite pentru a genera fi?ierul executabil next - execut? o linie de cod, f?r? a intra în alte func?ii step - execut? o linie de cod, intrând în func?ii run - execut? programul care este în depanare quit - iese din gdb watch - permite examinarea unei variabile a programului ori de câte ori variabila se modific? break - seteaz? un punct de oprire (breakpoint). De fiecare dat? când programul ajunge în acest punct, se opre?te info break – vezi breakpoint-urile deja setate delete, clear – sterge un breakpoint make - recompileaz? programul f?r? a ie?i din gdb shell - execut? comenzi shell help - informa?ii despre comenzi gdb suport? comenzile de editare ale shell-ului: TAB pentru completarea automat? a comenzilor, s?ge?ile sus, jos pentru navigarea printre comenzile care au fost deja introduse. Foarte multe comenzi permit prescurtari (ex. r in loc de run, b in loc de breakpoint, etc.) Exemplu de sesiune gdb: Folosind un editor de text simplu, introduce?i urm?torul program: #include #include #include void my_print(char *); void my_print2(char *); main () { char my_string[] = "salut"; my_print (my_string); my_print2 (my_string); } void my_print (char *string) { printf ("Sirul este: %s\n", string); } void my_print2 (char *string) { char *string2; int size, i; size = strlen (string); string2 = (char *) malloc (size + 1); for (i = 0; i < size; i++) string2[size - i] = string[i]; string2[size + 1] = '\0'; printf ("Sirul afisat invers: %s\n", string2); } Compila?i programul cu numele salut (folosi?i -o) ?i executa?i-l. Observa?i c? a doua linie nu a afi?at ceea ce trebuia. A doua linie trebuia s? afi?eze: ?irul afi?at invers: tulas S? rezolv?m problema folosind gdb. Mai întâi, recomplila?i programul folosind op?iunea -g: gcc -g -o salut salut.c Apela?i gdb: gdb salut sau gdb (gdb) file salut care va încarca programul salut. Încerca?i apoi: (gdb) run ve?i observa c? programul ruleaz? la fel, gdb specificând ?i codul de ie?ire al programului. Codul de ie?ire poate fi setat la o valoare dorit? dac? în program, func?ia main este de tip int ?i este prezent? în program o instruc?iune return. Pentru a g?si problema, seta?i un breakpoint dup? instruc?iunea for din func?ia my_print2. Pentru aceasta, lista?i codul surs? folosind list: (gdb) list ! ap?sând Enter la promptul gdb ve?i repeta ultima comand? introdus? Observa?i c? fiecare linie începe cu un num?r. G?si?i num?rul liniei specificate, dup? care: (gdb) break nr Rula?i programul cu run. Ve?i observa c? programul se opre?te la linia specificat?. Ca s? pute?i s? v? da?i seama de problem?, urm?ri?i valoarea lui string2[size-i]: (gdb) watch string2[size-i] în acest moment, gdb va opri programul de fiecare dat? când valoarea specificat? se modific?. Întrucât am mai setat un breakpoint la aceea?i linie, programul se va opri de dou? ori în acela?i loc. Pentru a elimina aceast? redundan??, elimina?i breakpoint-ul cu: (gdb) disable 1 în care 1 înseamn? primul breakpoint. Numerele breakpoint-urilor sunt date în ordinea în care au fost setate. Continua?i execu?ia cu: (gdb) cont ! pute?i afi?a valoarea oric?rei variabile sau expresii folosind comanda print expresie (ex. print i sau print (size - i)). Continua?i executia, observând de fiecare dat? valorile expresiei afi?ate de watch. Se observ? c? nu este nici o valoare asignat? pentru string2[0]. Întrucât malloc ini?ializeaz? memoria alocat? cu 0, primul caracter al ?irului este 0. Reamintim c? în C, ?irurile se termin? cu \0. Acum, c? v-a?i dat seama de problem?, g?si?i o cale de a o rezolva. 2.3. make make este unul din cele mai importante unelte folosite în dezvoltarea de soft pentru Linux. make este un program care ?ine cont de dependen?ele dintre fi?iere ?i actualizeaz? doar acele fi?iere care au fost modificate de la ultima compilare. make genereaz? comenzi folosind un fi?ier descriptiv numit Makefile (e valabil si makefile) . Aceste comenzi sunt executate de shell. Acest fi?ier con?ine seturi de reguli care trebuie urmate când se actualizeaz? un program. 2.3.1. Formatul makefile makefile e format din mai multe intr?ri. O intrare are forma: Instructiunile pentru make se gasesc intr-un Makefile/makefile si determina ce actiuni trebuie facute pentru a satisface anumite cerinte. Sintaxa unui makefile este de forma: Target... : dependencies ... command command ... unde: • target = numele unui fisier ce trebuie generat sau numele unei actiuni • dependencies = lista de actiuni (fisiere) ce trebuie indeplinite (ce trebuie sa existe) pentru a se realiza target-ul – make determina daca trebuie re-executat target-ul daca una dintre dependete s-a modificat (unul din fisierele din lista de dependente s-a modificat) • command = comanda ce duce la realizarea target-ului (de obicei la comenzi sunt trecute comenzile de compilare care duc la realizarea target-ului) • = caracterul tab Exemplu 1: in fisierul Makefile: pregatire: mancare curatenie echo "Putem primi musafiri" touch pregatire mancare: cumparaturi echo "Gata MANCARE" touch mancare curatenie: echo "Gata CURATENIE" touch curatenie cumparaturi: echo "Gata CUMPARATURI" touch cumparaturi pierde_cumparaturi: rm -f cumparaturi echo "pierdut CUMPARATURI" clean: rm -f cumparaturi rm -f curatenie rm -f mancare rm -f pregatire echo "Gata ANULARE" Exemplu 2: in fisierul Makefile: build: a2 a2: a21.o a22.o gcc a21.o a22.o -o a2 a21.o: a21.c gcc -c a21.c a22.o: a22.c gcc -c a22.c run: a2 ./a2 clean: rm -f a2 a21.o a22.o Exercitiul 1 Introduceti urmatorul program, compilati si executati fisierul rezultat. main() { int i; printf("\t Numarul \t\t patratul numarului\n\n"); for (i=0; i<=25; ++i) printf("\t %d \t\t\t %d \n",i,i*i); } Exercitiul 2 Introduceti urmatorul program ( utilizeaza o librarie matematica), compilati si executati fisierul rezultat. #include main() { int i; printf("\t Numarul \t\t patratul numarului\n\n"); for (i=0; i<=360; ++i) printf("\t %d \t\t\t %d \n",i, sqrt((double) i)); } Exercitiul 3 Priviti in directorul /lib si in directorul /usr/lib si vedeti ce librarii sunt disponibile. - utilizati comanda man pentru a vedea detalii despre aceste librarii - explorati fiecare librarie in parte si vedeti ce contine prin lansarea comenzii ar t Exercitiul 4 Priviti in directorul /usr/include si vedeti ce librarii sunt disponibile. - utilizati comanda more sau comanda cat pentru a vedea aceste fisiere - explorati fiecare fisier header si vedeti ce contine Exercitiul 5 Considerand ca avem un program C a carui functie main este in main.c si alte functii in fisierele input.c si respectiv output.c - ce comenti veti utiliza pentru compilarea si linkeditarea acestui program? - cum veti modifica aceste comenzi astfel incat sa legati si libraria denumita process1 din directorul sistem de librarii? - cum veti modifica aceste comenzi astfel incat sa legati si libraria denumita process2 din directorul home? 3. Întreb?ri ?i aplica?ii 3.1. Explica?i comanda: gcc -o file file.c -lm 3.2. Explica?i utilitatea folosirii op?iunii "-g" la compilarea unui program. 3.3. Explica?i linia: string2=(char *) malloc(size + 1); din programul din platform?. 3.4. Cum se lanseaz? ?i cum se opre?te debugerul gdb? 3.5. Explica?i comenzile de baz? "watch" ?i "shell" din debug-erul gdb. 3.6. Cum ?i cu ce scop se folose?te comanda "break" ?n gdb? 3.7. Explica?i diferen?a dintre comenzile "next" ?i "step" ale gdb-ului. 3.8. Explica?i diferen?a dintre comenzile "run" ?i "make" ale gdb-ului. 3.9. Explica?i ce face comanda "cont" în gdb ?i cum se anuleaz? un breakpoint. 3.10. Ce este gcc-ul ?i la ce se folose?te op?iunea "-pg"? 3.11. Scrie?i un program care ordoneaz? cresc?tor un ?ir de numere introduse de la tastatur?. 3.12. Scrie?i un program care copiaz? fisiere. 3.13. Scrie?i un program care afi?eaz? num?rul de apari?ii ale unui sub?ir într-un ?ir. 3.14. S? se scrie un program care afi?eaz? în ordine invers? literele unui ?ir de caractere citit de la tastatur?. 3.15. S? se scrie un program care afi?eaz? valorile func?iei x*sin(x) pe intervalul [0,10], cu pasul 0.1. 3.16. S? se scrie un program care genereaz? 100 de numere aleatoare (numere întregi inferioare lui 10000). 3.17. S? se scrie un program care sorteaz? cresc?tor 10 numere introduse de la tastatur?. 3.18. S? se scrie un program care num?r? elementele negative dintr-o secven?? de n elemente. 3.19. S? se scrie un program care s? transforme literele unui ?ir în litere mici. 3.20. S? se scrie un program care s? calculeze x la puterea y (unde x ?i y pot fi introduse de la tastatur?).