Nu există vreo diferență tehnică
între cele două tipuri de fișiere; compilatorul
îți permite să incluzi un fișier .c într-o altă sursă, sau să compilezi un fișier .h, dacă asta
este ceea ce dorești să faci.
Există însă o diferență culturală:
Declarările
se găsesc în fișierele .h; acestea pot fi vizualizate ca o interfață către orice este implementat în fișierul .c corespondent.Definițiile
se găsesc în fișierele .c; ele implementează interfața specificată în fișierul .h.Un fișier .h poate (și este de obicei) inclus în mai multe fișiere .c. În cazul în care o funcție este definită în cadrul acestuia, va ajunge în mai multe fișiere cod obiect, iar linker-ul se 'va plânge' de multiple definiții ale simbolului respectiv. Din acesta cauză definițiile nu trebuie să fie în fișierele header.
La calea ~/uso-lab/04-appdev/support/need-to-know
avem 3 fișiere cod sursă C și 2 fișiere header.
student@uso:~/uso-lab/04-appdev/support/need-to-know$ ls a.c a.h b.c b.h main.c
Fișierul main.c
este cel care conține și funcția main, adică punctul de intrare în program.
student@uso:~/uso-lab/04-appdev/support/need-to-know$ cat main.c #include <stdio.h> #include <stdlib.h> #include "a.h" #include "b.h" int main() { printf("globalVar = %d\n", globalVar); print1(); double value = print2(0.5); printf("value = %f\n", value); return 0; }
Se observă faptul că pe lângă bibliotecile standard stdio
și stdlib
, main.c
mai are nevoie de câteva resurse externe și
anume a.h
și b.h
. În aceste 2 fișiere găsim declarații de funcții și/sau variable pe care vrem să le folosim în main.c
.
a.h
#ifndef A #define A extern int globalVar; void print1(); #endif
b.h
#ifndef B #define B double print2(double angle); #endif
Fiecare fișier header vine asociat cu un fișier cod sursă în care sunt definite toate funcțiile/variabilele declarate în headere.
Fișierele cod sursă includ toate headerele asociate. Spre exemplu, a.c
include fișierul a.h
, la fel și pentru b.c
.
Trebuie reținut faptul că pentru a putea avea un program funcțional, trebuie să fie compilate toate fișierele cod sursă.
În această situație, vom compila toate cele 3 fișiere cod sursă într-un singur binar. Pentru asta folosim tot gcc
.
student@uso:~/uso-lab/04-appdev/support/need-to-know$ gcc main.c a.c b.c -o main student@uso:~/uso-lab/04-appdev/support/need-to-know$ ls a.c a.h b.c b.h main main.c
main
și asigurați-vă că rularea lui se termină cu succes.bad
fără parametri care nu întoarce nimic, în fișierul a.h
(Atenție: doar declarația!).main.c
, înainte de linia return 0
, apelați această funcție (bad()
).bad()
din funcția main
din fișierul main.c
.
Există câteva biblioteci care sunt linkate automat atunci când compilăm un program C. Una dintre acestea este libc
. Majoritatea
programelor C folosesc funcții din aceastea bibliotecă și s-a hotărât să fie linkată întotdeauna, însă aceasta nu este singura bibliotecă
externă pe care putem să o folosim. Putem să creăm noi o bibliotecă și să o linkăm programului nostru sau să folosim o bibliotecă deja
existentă cum ar fi biblioteca ce conține funcții matematice math
.
Pentru a folosi biblioteca math
în programul nostru, trebuie să facem 2 lucruri.
math.h
în program, pentru a putea preciză că se vor folosi funcții declarate acolo.math.h
și că vrem ca binarul nostru să fie linkat cu biblioteca math
.Urmăriți tutorialul de mai jos.
În directorul ~/uso-lab/04-appdev/support/need-to-know
creați un nou director cu numele using-math
și intrați în el.
student@uso:~/uso-lab/04-appdev/support/need-to-know$ mkdir using-math student@uso:~/uso-lab/04-appdev/support/need-to-know$ cd using-math student@uso:~/uso-lab/04-appdev/support/need-to-know/using-math$ ls student@uso:~/uso-lab/04-appdev/support/need-to-know/using-math$
Creați aici un fișier simplu numit main.c
cu următorul conținut:
#include <stdio.h> #include <math.h> int main(void) { float x; printf("Give me a number: "); scanf("%f", &x); printf("The square root for it is: %f\n", sqrt(x)); return 0; }
math.h
pentru a putea folosi funcția sqrt
pentru calcularea radicalului unui număr.
Compilați programul folosind gcc
astfel încât executabilul obținut să se numeasca main
.
student@uso:~/uso-lab/04-appdev/support/need-to-know/using-math$ ls main.c student@uso:~/uso-lab/04-appdev/support/need-to-know/using-math$ gcc main.c -o main /tmp/ccRBgYrK.o: In function `main': main.c:(.text+0x35): undefined reference to `sqrt' collect2: error: ld returned 1 exit status
Eroarea apare din cauză că gcc
nu știe unde să localizeze funcția sqrt
pe care noi o folosim în program. Pentru a rezolva această
eroare, atunci când compilăm programul trebuie să dăm un argument în plus lui gcc
care să specifice că vrem să linkăm executabilul
nostru cu biblioteca math
. Practic, îi promitem compilatorului că la rularea programului, funcția sqrt
va exista pentru că îi spunem
noi acum unde este.
student@uso:~/uso-lab/04-appdev/support/need-to-know/using-math$ ls main.c student@uso:~/uso-lab/04-appdev/support/need-to-know/using-math$ gcc main.c -o main -lm student@uso:~/uso-lab/04-appdev/support/need-to-know/using-math$ ./main Give me a number: 1234 The square root for it is: 35.128336
Acum programul a compilat cu succes și am putut să-l și rulăm.
Până acum am compilat fișierele sursă folosind comanda gcc
direct din terminal. Acest proces poate deveni anevoios în momentul în
care proiectul conține mai multe fișiere sursă sau dacă codul depinde de niște variable externe. Nu vrem să scriem comanda de compilare
de fiecare dată, vrem să automatizăm acest proces. Pentru asta folosim fișierele Makefile
.
Dacă avem într-un directorul fișierul test.c
și vrem să-l compilăm, pur și simplu rulăm comanda gcc test.c
acolo și obținem un
executabil cu numele a.out
. Pot apărea situații când compilarea unui program este mai complexă. Spre exemplu, mai sus, existau 3 fișiere
sursă pe care trebuia să le compilăm ca programul să funcționeze.
Un exemplu de fișier Makefile simplu care ar automatiza acest proces este următorul:
main: gcc main.c a.c b.c -o main clean: rm main
Fișierul Makefile de mai sus conține 2 reguli: main
și clean
. Regula main va fi folosită pentru a compila programul, pe
când regula clean
va fi folosită pentru a șterge toate fișierele generate în urma compilării.
~/uso-lab/04-appdev/support/need-to-know/
. Denumiți fișierul Makefile
.
Pentru a rula regula main
trebuie să scriem comanda make main
.
student@uso:~$ cd ~/uso-lab/04-appdev/support/need-to-know student@uso:~/uso-lab/04-appdev/support/need-to-know$ ls a.c a.h b.c b.h main.c Makefile student@uso:~/uso-lab/04-appdev/support/need-to-know$ make main gcc main.c a.c b.c -o main student@uso:~/uso-lab/04-appdev/support/need-to-know$ ls a.c a.h b.c b.h main main.c Makefile
Vedem acum că a fost creat executabilul main
pe care putem să-l rulăm în continuare.
main
și asigurați-vă că funcționează.run
în Makefile care să aibă drept comandă asociată chiar comanda cu care rulăm programul main
. Nu treceți mai departe până când partea aceasta nu este clară.main
folosind una dintre regulile definite în Makefile.
Dacă se va rula în terminal doar comanda make
(fără a fi urmată de vreun argument), atunci se va executa prima regulă găsită în Makefile,
în cazul acesta tot regula main
.
student@uso:~$ cd ~/uso-lab/04-appdev/support/need-to-know student@uso:~/uso-lab/04-appdev/support/need-to-know$ ls a.c a.h b.c b.h main.c Makefile student@uso:~/uso-lab/04-appdev/support/need-to-know$ make gcc main.c a.c b.c -o main student@uso:~/uso-lab/04-appdev/support/need-to-know$ ls a.c a.h b.c b.h main main.c Makefile
~/uso-lab/04-appdev/support/need-to-know/
creați un Makefile care să conțină regulile main
, run
și clean
. Nu treceți mai departe până când asistentul confirmă că este în regulă.
Înainte de a parcurge această parte a secțiunii, rulați comanda cd ~/my-awesome-project
.
Vom crea acum 2 fișiere noi în acest director. Le vom numi tom.txt
și jerry.txt
. Scrieți câte un mesaj în fiecare dintre ele.
student@uso:~/my-awesome-project$ touch tom.txt student@uso:~/my-awesome-project$ touch jerry.txt (..) # aici editați fișierele
Vrem să vedem statusul repository-ul de git. Folosim comanda git status
.
student@uso:~/my-awesome-project$ git status # On branch master # Untracked files: # (use "git add <file>..." to include in what will be committed) # # jerry.txt # tom.txt nothing added to commit but untracked files present (use "git add" to track)
Scopul nostru acum este de a crea 2 commit-uri, unul care să se refere la adăugarea fișierului tom.txt
, iar celălalt la adăugarea lui jerry.txt
added tom.txt file
și celălalt cu added jerry.txt file
. Verificați că cele 2 commit-uri s-au efectuat cu succes (hint: git log
).
La rularea comenzii git log
va trebui să primiți un output similar cu următorul:
commit 368bf97efe83109491a3f21874aa763a8f1fe682 Author: student <student@uso> Date: Wed Oct 9 14:30:34 2019 +0300 added jerry.txt file commit 55e8b77f46ae355d67e8ed072d2e87c367a078c4 Author: student <student@uso> Date: Wed Oct 9 14:30:23 2019 +0300 added tom.txt file commit a67a3fb85fea113b25e412f813382c4cbaf196f0 Author: student <student@uso> Date: Wed Oct 9 14:26:38 2019 +0300 added main file commit 64bccb03c2f3a8e76730582676bd944902a0c1eb Author: student <student@uso> Date: Mon Oct 7 15:49:20 2019 +0300 Initial commit