This shows you the differences between two versions of the page.
|
uso:laboratoare:new:04-appdev:demo [2018/10/23 21:16] razvan.deaconescu created |
uso:laboratoare:new:04-appdev:demo [2019/10/24 09:59] (current) adrian.zatreanu [Demo] |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| ===== Demo ===== | ===== Demo ===== | ||
| - | TODO | + | În această secțiune vom urmări să obținem o mai bună înțelegere asupra |
| + | procesului compilării precum și asupra utilității și utilizării fișierelor Makefile. | ||
| + | |||
| + | Pentru această secțiune trebuie să vă asigurați că sunteți în directorul potrivit. Rulați comanda | ||
| + | |||
| + | <code bash> | ||
| + | student@uso:~$ cd ~/uso-lab/04-appdev/support/demo/ | ||
| + | </code> | ||
| + | |||
| + | ==== Compilarea codului în C/C++ ==== | ||
| + | |||
| + | După cum a fost precizat și înainte, compilatorul cel mai folosit pentru programele | ||
| + | scrise în C este gcc, iar pentru C++, g++. | ||
| + | |||
| + | Să presupunem că avem următorul fișier sursă ''print.c''. | ||
| + | |||
| + | <code bash> | ||
| + | #include <stdio.h> | ||
| + | #include <stdlib.h> | ||
| + | |||
| + | int main(void) { | ||
| + | printf("USO Rules! <3\n"); | ||
| + | |||
| + | return 0; | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | În urma rulării comenzii ''gcc print.c'', procesul de compilare trece prin toate cele | ||
| + | patru faze intermediare rezultând fișierul executabil a.out. | ||
| + | |||
| + | <code bash> | ||
| + | student@uso:~$ ls | ||
| + | print.c | ||
| + | student@uso:~$ gcc print.c | ||
| + | student@uso:~$ ls | ||
| + | a.out print.c | ||
| + | root@ebp:~$ | ||
| + | </code> | ||
| + | |||
| + | ''a.out'' este denumirea default în situația în care nu este specificat care să fie | ||
| + | numele executabilului. Pentru aceasta folosim parametrul ''[-o output_filename]'' | ||
| + | la rularea comenzii. Parametrul poate să se găsească la orice poziție în cadrul | ||
| + | comenzii, dar este obligatoriu să fie urmat întotdeauna de numele dorit. | ||
| + | |||
| + | <code bash> | ||
| + | student@uso:~$ gcc print.c -o print | ||
| + | student@uso:~$ ls | ||
| + | a.out print print.c | ||
| + | student@uso:~$ | ||
| + | </code> | ||
| + | |||
| + | Observăm ca fișierul ''a.out'' este un fișier binar folosind comanda ''file''. | ||
| + | |||
| + | <code bash> | ||
| + | student@uso:~$ file a.out | ||
| + | a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=c5ad78cfc1de12b9bb6829207cececb990b3e987, not stripped | ||
| + | </code> | ||
| + | |||
| + | Procesul de compilare are patru etape: preprocesare, compilare, asamblare și | ||
| + | link-editare. La o rulare normală a comenzii gcc se trece prin toate aceste faze, | ||
| + | însă există și posibilitatea de a întrerupe procesul la finalul uneia anume. | ||
| + | * folosirea parametrului -c spune compilatorului să se oprească după faza de asamblare și să nu linkeze codul. | ||
| + | |||
| + | <code bash> | ||
| + | student@uso:~$ gcc -c print.c | ||
| + | student@uso:~$ ls | ||
| + | print.c print.o | ||
| + | student@uso:~$ cat print.o | ||
| + | ELF>�@@ | ||
| + | UH��H�=��]�USO Rules! <3GCC: (Debian 8.2.0-7) 8.2.0zRx | ||
| + | R �A�C | ||
| + | �� $print.cmain_GLOBAL_OFFSET_TABLE_puts�������� | ||
| + | |||
| + | �������� .symtab.strtab.shstrtab.rela.text.data.bss.rodata.comment.note.GNU-stack.rela.eh_frame @@0 | ||
| + | &WW1W90eB�W�R@@ | ||
| + | � | ||
| + | �)Xaroot@ebp:~/Documents/uso/lab_04/support# | ||
| + | </code> | ||
| + | |||
| + | Se observă că rezultatul final este un fișier cu cod obiect. | ||
| + | * folosirea parametrului -S instruiește compilatorul să nu asambleze codul. | ||
| + | <code bash> | ||
| + | student@uso:~$ gcc -S print.c | ||
| + | student@uso:~$ ls | ||
| + | print.c print.s | ||
| + | student@uso:~$ cat print.s | ||
| + | .file "print.c" | ||
| + | .text | ||
| + | .section .rodata | ||
| + | .LC0: | ||
| + | .string "USO Rules! <3" | ||
| + | .text | ||
| + | .globl main | ||
| + | .type main, @function | ||
| + | main: | ||
| + | .LFB5: | ||
| + | .cfi_startproc | ||
| + | pushq %rbp | ||
| + | .cfi_def_cfa_offset 16 | ||
| + | .cfi_offset 6, -16 | ||
| + | movq %rsp, %rbp | ||
| + | .cfi_def_cfa_register 6 | ||
| + | leaq .LC0(%rip), %rdi | ||
| + | call puts@PLT | ||
| + | movl $0, %eax | ||
| + | popq %rbp | ||
| + | .cfi_def_cfa 7, 8 | ||
| + | ret | ||
| + | .cfi_endproc | ||
| + | .LFE5: | ||
| + | .size main, .-main | ||
| + | .ident "GCC: (Debian 8.2.0-7) 8.2.0" | ||
| + | .section .note.GNU-stack,"",@progbits | ||
| + | student@uso:~$ | ||
| + | </code> | ||
| + | * folosirea parametrului -E oprește procesul de compilare după faza de preprocesare. | ||
| + | |||
| + | <note> | ||
| + | Toate opțiunile utilitarului ''gcc'' pot fi regăsite în pagina de manual (''man gcc''). | ||
| + | </note> | ||
| + | |||
| + | ==== Makefile ==== | ||
| + | |||
| + | Utilitarul de automatizare cel mai folosit pentru aplicațiile C/C++ este **make**. | ||
| + | În general numele unui fișier makefile este Makefile, însă te poți afla în | ||
| + | situația în care ai nevoie de mai multe astfel de fișiere, moment în care | ||
| + | pentru a alege unul dintre ele putem rula comanda make cu parametrul -f, urmat | ||
| + | de numele makefile-ului pe care dorim să îl folosim. | ||
| + | |||
| + | <code> | ||
| + | student@uso:~$ ls | ||
| + | Makefile Makefile.win | ||
| + | student@uso:~$ make -f Makefile.win | ||
| + | (..) | ||
| + | </code> | ||
| + | |||
| + | <note> | ||
| + | Formatul unui fișier makefile este următorul: | ||
| + | |||
| + | Regulă: dependențe\\ | ||
| + | <TAB> comandă | ||
| + | |||
| + | Fișierul poate să conțină una sau mai multe astfel de linii. | ||
| + | </note> | ||
| + | |||
| + | Avem fișierul sursă ''print.c''. Un exemplu de Makefile care autmatizeaza procesul de compilare | ||
| + | al acestuia este următorul: | ||
| + | |||
| + | <code> | ||
| + | build: print.c | ||
| + | gcc -Wall print.c -o print | ||
| + | |||
| + | clean: | ||
| + | rm print | ||
| + | </code> | ||
| + | |||
| + | Acest fișier conține 2 reguli: una de **build** prin care se compilează fișierul ''print.c'' și una de ''clean'' | ||
| + | pe care o putem folosi să ștergem fișierele generate în urma compilării (cum ar fi fișierele executabile, obiect, etc.). | ||
| + | |||
| + | Pentru a rula regula numită ''build'', rulăm comanda ''make build''. | ||
| + | |||
| + | <code bash> | ||
| + | student@uso:~$ make build | ||
| + | gcc -Wall print.c -o print | ||
| + | student@uso:~$ ls | ||
| + | Makefile print print.c | ||
| + | student@uso:~$ | ||
| + | </code> | ||
| + | |||
| + | Pentru a șterge fișierul generat (''print''), rulăm comanda ''make clean''. | ||
| + | |||
| + | <code bash> | ||
| + | student@uso:~$ make clean | ||
| + | rm print | ||
| + | student@uso:~$ ls | ||
| + | Makefile print.c | ||
| + | student@uso:~$ | ||
| + | </code> | ||
| + | |||
| + | Dacă nu se precizează niciun argument comenzii ''make'', atunci aceasta va rula prima regulă întâlnită în fișier, adică regula ''build''. | ||
| + | |||
| + | <code bash> | ||
| + | student@uso:~$ make | ||
| + | gcc -Wall print.c -o print | ||
| + | student@uso:~$ ls | ||
| + | Makefile print print.c | ||
| + | student@uso:~$ | ||
| + | </code> | ||
| + | |||
| + | <note> | ||
| + | Regulile din Makefile pot avea și ''dependențe''. Aceastea sunt opționale, însă sunt încurajate. | ||
| + | Ele reprezintă, de fapt, fișiere și/sau reguli necesare pentru a putea rula o altă regulă. | ||
| + | Practic, la rularea unei reguli se verifică dacă fișierul din dependență există. | ||
| + | Dacă acesta există, putem rula regula, dacă nu, se caută o altă regulă cu acel nume și se va | ||
| + | rula acea regulă (dacă dependențele ei sunt îndeplinite, dacă nu, se continuă lanțul de dependențe). | ||
| + | </note> | ||
| + | |||
| + | Să considerăm următorul Makefile: | ||
| + | |||
| + | <code bash> | ||
| + | build: utils.o hello.o help.o | ||
| + | gcc utils.o help.o hello.o -o hello | ||
| + | |||
| + | all: | ||
| + | gcc simple_hello.c -o simple | ||
| + | |||
| + | utils.o: utils.c | ||
| + | gcc -c utils.c | ||
| + | |||
| + | hello.o: hello.c | ||
| + | gcc -c hello.c | ||
| + | |||
| + | help.o: help.c | ||
| + | gcc -c help.c | ||
| + | |||
| + | clean: | ||
| + | rm -f *.o hello | ||
| + | </code> | ||
| + | |||
| + | Prima regulă care va fi rulată este ''build''. Când se încearcă rularea ei se verifică | ||
| + | existența fișierelor obiect utils.o, hello.o și help.o. Cum ele nu au fost încă | ||
| + | generate se caută în fișier o regulă cu numele lor. După cum se observă, aceste | ||
| + | reguli se găsesc mai jos în Makefile și vor fi executate deoarece ele | ||
| + | au ca dependențe doar fișiere cod sursă (despre care știm deja că există), comenzile | ||
| + | aferente lor generând codul obiect necesar rulării regulii ''build''. | ||
| + | |||
| + | De reținut este și faptul că ''make'' verifică dacă o dependență a fost sau nu modificată | ||
| + | după momentul în care a fost creată. În caz afirmativ aceasta va fi regenerată, | ||
| + | altfel, pentru eficientizare, aceasta va fi reutilizată. | ||
| + | |||
| + | Spre exemplu, dacă avem fișierul Makefile de mai sus, am rulat deja o dată regula ''make build'' | ||
| + | și după modificăm fișierul ''utils.c'', atunci la următoarea rulare a regulii ''make build'' se | ||
| + | va rula mai întâi (automat) regula ''utils.o'' și abia dupa ''make build''. | ||