|
|
Linia 1: |
Linia 1: |
− | = Obiective =
| |
| | | |
− | În urma parcurgerii acestui laborator studentul va fi capabil:
| |
− | * să utilizeze stream-urile standard ale unui proces în Linux și în programe C;
| |
− | * să utilizeze funcționalitatea de bază a unui sistem de revision control - Git.
| |
− | * să înțeleagă funcționalitatea preprocesorului și să folosească directive de preprocesare;
| |
− |
| |
− | = [https://en.wikipedia.org/wiki/Standard_streams Stream-urile standard] ale unui proces =
| |
− |
| |
− | [[Fișier:standard_streams.png]]
| |
− |
| |
− | După cum se poate observa în schema de mai sus, un proces care rulează într-un sistem de operare dispune de trei stream-uri de intrare ieșire implicite:
| |
− | * Stream-ul standard de intrare ('''stdin''') - este utilizat pentru a introduce date în proces și este implicit atașat tastaturii;
| |
− | * Stream-ul standard de ieșire ('''stdout''') - este utilizat pentru a extrage rezultate ale execuției procesului și este implicit atașat consolei;
| |
− | * Stream-ul standard de eroare ('''stderr''') - este tradițional utilizat pentru a extrage informații legate de erorile apărute în execuția procesului și este implicit atașat consolei;
| |
− |
| |
− | Se observă că ambele stream-uri de ieșire sunt implicit trimise către consolă, unde utilizatorul procesului le poate vedea. Motivul pentru care sunt două este acela că aceste stream-uri pot fi redirectate către alte procese sau către fișiere, astfel se poate face o separare simplă și rapidă a mesajelor de eroare, în scopul depanării programului.
| |
− |
| |
− | Toate aceste stream-uri sunt formatate pe octet (byte). În cele mai multe din cazuri, acești bytes reprezintă caractere codate [https://en.wikipedia.org/wiki/ASCII ASCII].
| |
− |
| |
− | == Scrierea pe '''stdout''' ==
| |
− |
| |
− | Pentru a scrie pe stream-ul standard de ieșire se folosește generic funcția <code style="color: green">fprintf</code>:
| |
− |
| |
− | Funcția <code style="color: green">fprintf</code> este definită în header-ul <code style="color: blue">stdio.h</code> (standard input/output) și este implimentată în biblioteca standard a limbajului C. Pentru scrierea pe stream-ul standard de ieșire, se folosește în felul următor:
| |
− |
| |
− | <syntaxhighlight lang="C">
| |
− | fprintf(stdout, "[format]", val1, val2, ...);
| |
− | </syntaxhighlight>
| |
− |
| |
− |
| |
− | <div class="sfat">'''<font color="blue">Observație:</font>''' Deoarece scrierea pe stream-ul standard de ieșire este atât de utilizată, s-a creat o funcție specială pentru aceasta, numită <code style="color: green">printf</code>:
| |
− | <syntaxhighlight lang="C">
| |
− | printf("[format]", val1, val2, ...);
| |
− | </syntaxhighlight>
| |
− | </div>
| |
− |
| |
− | Formatul reprezintă modelul de date pe care îl dorim afișat în consolă.
| |
− |
| |
− | Specificatorul de format pentru <code style="color: green">fprintf</code>, respectiv <code style="color: green">printf</code> urmează următorul prototip:
| |
− |
| |
− | %[flags][width][.precision][length]specifier
| |
− |
| |
− | unde caracterul ''specifier'' de la sfârșit este componenta cea mai importantă deoarece definește ce caractere sunt extrase din stream, interpretarea lor, și tipul de date corect al argumentului corespunzător:
| |
− |
| |
− | {| class="wikitable"
| |
− | ! ''specifier'' !! Afișare !! Exemplu
| |
− | |-
| |
− | |i, d || întreg decimal cu semn || -694
| |
− | |-
| |
− | |u || întreg decimal fără semn || 1659
| |
− | |-
| |
− | |o || întreg în baza 8 || 254
| |
− | |-
| |
− | |x || întreg în baza 16 (litere mici) || 1af43
| |
− | |-
| |
− | |X || întreg în baza 16 (litere mari) || 1AF43
| |
− | |-
| |
− | |f || număr în virgulă mobilă || 23.65
| |
− | |-
| |
− | |e || număr în virgulă mobilă, notație științifică (mantisa - exponent), litere mici || 3.9265e+2
| |
− | |-
| |
− | |E || număr în virgulă mobilă, notație științifică (mantisa - exponent), litere mari || 3.9265E+2
| |
− | |-
| |
− | |g || număr în virgulă mobilă, cea mai scurtă reprezentare (%f sau %e) || 23.65
| |
− | |-
| |
− | |G || număr în virgulă mobilă, cea mai scurtă reprezentare (%F sau %E) || 23.65
| |
− | |-
| |
− | |a || număr în virgulă mobilă în baza 16, litere mici || -0xc.90fep-2
| |
− | |-
| |
− | |A || număr în virgulă mobilă în baza 16, litere mari || -0xc.90FEP-2
| |
− | |-
| |
− | |c || caracter || a
| |
− | |-
| |
− | |s || șir de caracere || castravete
| |
− | |-
| |
− | |p || adresă în memorie (pointer) || 00000000b8000000
| |
− | |-
| |
− | |n || nu se printează nimic || numărul de caractere citit până în momentul curent este stocat în variabila specificată
| |
− | |-
| |
− | |% || % ||Un caracter % urmat de alt caracter % afișează un singur %.
| |
− | |}
| |
− |
| |
− | Specificatorul de format poate conține și sub-specificatori: ''flags'', ''width'', ''.precision'' și ''modifiers'' (în această ordine), care sunt opționale și respectă următoarele reguli:
| |
− |
| |
− | {| class="wikitable"
| |
− | ! ''flag'' !! Descriere
| |
− | |-
| |
− | | - || Aliniere la stânga cu dimensiunea dată de câmpul ''width'' (implicit aliniera este la dreapta). Vezi descrierea lui ''width''.
| |
− | |-
| |
− | | + || Forțează printarea unui semn (+ sau -), chiar și pentru valori pozitive (implicit se afișează semnul doar pentru numere negative).
| |
− | |-
| |
− | | (spațiu) || Dacă semnul nu se afișează, în locul lui se inserează un spațiu.
| |
− | |-
| |
− | | # || Folosit cu o, x, sau X, printează 0, 0x sau respectiv 0X în fața valorii, pentru numere diferite de 0. Folosit cu a, A, e, E, f, g sau G, forțează printarea punctului decimal, chiar dacă după el nu mai urmează alte cifre. Implicit, dacă după punct nu mai urmează alte cifre (numărul este întreg), punctul nu se mai afișează.
| |
− | |-
| |
− | | 0 || Când se specifică o dimensiune (''width''), numărul se completează la stânga cu 0 în loc de spațiu. Vezi descrierea lui ''width''.
| |
− | |}
| |
− |
| |
− | {| class="wikitable"
| |
− | ! ''width'' !! Descriere
| |
− | |-
| |
− | | (număr) || Numărul minim de caractere care trebuie scrise. Dacă lungimea numărului este mai mică decât această valoare, el este completat cu spații. Dacă lungimea este mai mare, numărul este afișat în întregime.
| |
− | |-
| |
− | | * || Valoarea ''width'' nu este specificată în string-ul ''format'', ci într-un argument suplimentar al funcției, care precede argumentul ce trebuie formatat.
| |
− | |}
| |
− |
| |
− | {| class="wikitable"
| |
− | ! ''.precision'' !! Descriere
| |
− | |-
| |
− | | (număr) || Pentru valori întregi (d, i, o, u, x, X), ''precision'' specifică numărul minim de cifre care trebuie afișate. Dacă numărul ce trebuie afișat este mai scurt decât această valoare, rezultatul este completat la stânga cu cifra 0. Dacă numărul este mai lung, el este afișat în întregime. O precizie 0 înseamnă că pentru valoarea 0 nu se va afișa nimic. Pentru specificatorii a, A, e, E, f și F, aceasta este numărul de cifre ce a fi afișat după punct (implicit valoarea aceasta este 6). Pentru specificatorii g și G, acesta este numărul maxim de cifre semnificative ce trebuie afișat. Pentru s, acesta este numărul maxim de caractere ce trebuie afișat. Implicit toate caracterele sunt afișate, până la întâlnirea unui terminator de string. Dacă punctul este scris fără o valoare pentru precizie, aceasta se consideră implicit 0.
| |
− | |-
| |
− | | * || Valoarea ''precision'' nu este specificată în string-ul ''format'', ci într-un argument suplimentar al funcției, care precede argumentul ce trebuie formatat.
| |
− | |}
| |
− |
| |
− | Specificatorul ''length'' modifică dimensunea tipului de date. Tabelul următor arată tipurile folosite pentru a interpreta argumentele corespunzătoare, cu și fără specificatorul ''length'' (dacă un alt tip este folosit, se realizează promovarea sau conversia tipului, dacă este posibil):
| |
− | <table class="wikitable">
| |
− | <tr><th></th><th colspan="7">specifiers</th></tr>
| |
− | <tr><th><i>length</i></th><th><tt>d i</tt></th><th><tt>u o x X</tt></th><th><tt>f F e E g G a A</tt></th><th><tt>c</tt></th><th><tt>s</tt></th><th><tt>p</tt></th><th><tt>n</tt></th></tr>
| |
− | <tr><td><i>(none)</i></td><td class="yes"><tt>int</tt></td><td class="yes"><tt>unsigned int</tt></td><td class="yes"><tt>double</tt></td><td class="yes"><tt>int</tt></td><td class="yes"><tt>char*</tt></td><td class="yes"><tt>void*</tt></td><td class="yes"><tt>int*</tt></td></tr>
| |
− | <tr style="background-color: yellow;"><td><tt>hh</tt></td><td class="yes"><tt>signed char</tt></td><td class="yes"><tt>unsigned char</tt></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="yes"><tt>signed char*</tt></td></tr>
| |
− | <tr><td><tt>h</tt></td><td class="yes"><tt>short int</tt></td><td class="yes"><tt>unsigned short int</tt></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="yes"><tt>short int*</tt></td></tr>
| |
− | <tr><td><tt>l</tt></td><td class="yes"><tt>long int</tt></td><td class="yes"><tt>unsigned long int</tt></td><td class="no"></td><td class="yes"><tt>wint_t</tt></td><td class="yes"><tt>wchar_t*</tt></td><td class="no"></td><td class="yes"><tt>long int*</tt></td></tr>
| |
− | <tr style="background-color: yellow;"><td><tt>ll</tt></td><td class="yes"><tt>long long int</tt></td><td class="yes"><tt>unsigned long long int</tt></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="yes"><tt>long long int*</tt></td></tr>
| |
− | <tr style="background-color: yellow;"><td><tt>j</tt></td><td class="yes"><tt>intmax_t</tt></td><td class="yes"><tt>uintmax_t</tt></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="yes"><tt>intmax_t*</tt></td></tr>
| |
− | <tr style="background-color: yellow;"><td><tt>z</tt></td><td class="yes"><tt>size_t</tt></td><td class="yes"><tt>size_t</tt></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="yes"><tt>size_t*</tt></td></tr>
| |
− | <tr style="background-color: yellow;"><td><tt>t</tt></td><td class="yes"><tt>ptrdiff_t</tt></td><td class="yes"><tt>ptrdiff_t</tt></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="yes"><tt>ptrdiff_t*</tt></td></tr>
| |
− | <tr><td><tt>L</tt></td><td class="no"></td><td class="no"></td><td class="yes"><tt>long double</tt></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="no"></td></tr>
| |
− | </table>
| |
− |
| |
− | <div class="sfat">'''<font color="blue">Observație:</font>''' Tabelele de mai sus pot fi înțelese abia după parcurgerea cursurilor și laboratoarelor legate de tipuri și dimensiuni de date. Ele vor fi folosite pe parcursul cursului drept referință și nu trebuie memorate.</div>
| |
− |
| |
− | <div class="sfat">'''<font color="blue">Observație:</font>''' Rândurile marcate cu galben au fost introduse în standardul C99.</div>
| |
− |
| |
− | == Scrierea pe '''stderr''' ==
| |
− |
| |
− | Scrierea pe stream-ul standard de eroare se face identic cu scrierea pe '''stdout''', specificând numele streamului ca prim argument al funcției <code style="color: green">fprintf</code>:
| |
− |
| |
− | <syntaxhighlight lang="C">
| |
− | fprintf(stderr, "[format]", val1, val2, ...);
| |
− | </syntaxhighlight>
| |
− |
| |
− |
| |
− | <div class="sfat">'''<font color="blue">Observație:</font>''' Nu se poate folosi funcția <code style="color: green">printf</code> pentru scrierea pe stream-ul standard de eroare.</div>
| |
− |
| |
− | == Citirea din '''stdin''' ==
| |
− |
| |
− | Pentru a citi de pe stream-ul standard de intrare se folosește generic funcția <code style="color: green">fscanf</code>:
| |
− |
| |
− | Funcția <code style="color: green">fscanf</code> este definită în header-ul <code style="color: blue">stdio.h</code> (standard input/output) și se folosește în felul următor:
| |
− |
| |
− | <syntaxhighlight lang="C">
| |
− | fscanf(stdin, "[format]", val1, val2, ...);
| |
− | </syntaxhighlight>
| |
− |
| |
− |
| |
− | <div class="sfat">'''<font color="blue">Observație:</font>''' Deoarece citirea de pe stream-ul standard este atât de utilizată, s-a creat o funcție specială pentru aceasta, numită <code style="color: green">scanf</code>:
| |
− | <syntaxhighlight lang="C">
| |
− | scanf("[format]", val1, val2, ...);
| |
− | </syntaxhighlight>
| |
− | </div>
| |
− |
| |
− | Formatul reprezintă modelul de date pe care ne așteptăm să-l primim de la tastatură. Acesta este un șir de caractere unde fiecare caracter este tratat în următorul fel:
| |
− | * '''caracter alb''' - se va citi și ignora din stream orice caractere albe până la întâlnirea unui caracter care nu este alb (caracerele albe includ spații, linie nouă, caractere tab); un singur caracter alb în format validează oricâte caractere albe extrase din stream (inclusiv nici unul).
| |
− | * '''caracter care nu este alb, exceptând specificatorul de format (%)''' - orice caracter care nu este alb (spațiu, linie nouă sau tab) sau care nu face parte dintr-un specificator de format (care începe cu %) face ca următorul caracter să fie citit din stream, comparat cu acest caracter din format, iar dacă sunt identice, ele sunt ignorate și se continuă cu următorul caracter; dacă nu sunt identice, <code style="color: green">fscanf</code> se va opri cu eroare;
| |
− | * '''specificator de format''' - o secvență care începe cu caracterul "la sută" (%) indică un specificator de format, utilizat pentru a indica tipul și formatul unei date care trebuie citită și stocată într-una din variabilele specificate în apelul lui <code style="color: green">fscanf</code>.
| |
− |
| |
− | Specificatorul de format pentru <code style="color: green">fscanf</code> urmează următorul prototip:
| |
− |
| |
− | %[*][width][length]specifier
| |
− |
| |
− | unde caracterul ''specifier'' de la sfârșit este componenta cea mai importantă deoarece definește ce caractere sunt extrase din stream, interpretarea lor, și tipul de date corect al argumentului corespunzător:
| |
− |
| |
− | {| class="wikitable"
| |
− | ! ''specifier'' !! Descriere !! Caractere extrase din stream
| |
− | |-
| |
− | |i, u || întreg || Orice număr de cifre, opțional precedate de un semn (+ sau -). Baza zece este considerată implicit (0-9), dar un 0 ca prefix marchează baza ca fiind opt (0-7), iar "0x" ca prefix marchează baza șaisprezece (0-f).
| |
− | |-
| |
− | |d || întreg în baza 10 || Orice număr de cifre decimale (0-9), opțional precedate de un semn (+ sau -).
| |
− | |-
| |
− | |o || întreg în baza 8 || Orice număr de cifre octale (0-7), opțional precedate de un semn (+ sau -).
| |
− | |-
| |
− | |x || întreg în baza 16 || Orice număr de cifre hexazecimale (0-7, a-f, A-F), opțional precedate de "0x" sau "0X", și totul opțional precedat de un semn (+ sau -).
| |
− | |-
| |
− | |f, e, g, a (C99) || număr în virgulă mobilă || O serie de cifre decimale, conținând opțional un punct decimal, opțional precedat de un semn (+ sau -), opțional urmat de un caracter e sau E, și un întreg în baza 10. În standardul C99 se acceptă și numere în virgulă mobilă în baza hexazecimală, precedate de 0x sau 0X.
| |
− | |-
| |
− | |c || caracter || Următorul caracter. Dacă '''width''' este specificat, se vor citi exact '''width''' caractere și vor fi stocate în locații succesive în vectorul primit ca argument. Nu este adăugat terminator de șir la final.
| |
− | |-
| |
− | |s || șir de caractere ||Orice număr de caractere care nu sunt albe, oprindu-se la primul caracter alb înlâlnit. Este automat adăugat terminator de șir la final.
| |
− | |-
| |
− | |p || adresă în memorie ||O secvență de caractere reprezentând o adresă în memorie. Formatul folosit depinde de sistem și de implementarea bibliotecii, dar este identic cu cel folosit de formatul %p în <code style="color: green">fprintf</code>.
| |
− | |-
| |
− | |[characters] || set de caractere || Orice număr de caractere dintre cele specificate între parantezele drepte. O liniuță (-) care nu este pe prima poziție între paranteze poate produce comportamente nedefinite în unele implementări de bibliotecă standard.
| |
− | |-
| |
− | |[^characters] || set de caractere negat || Orice număr de caractere care nu sunt specificate între parantezele drepte.
| |
− | |-
| |
− | |n || număr caractere citite || Nu este consumat nimic din stream. Numărul de caractere citit până în momentul curent este stocat în variabila specificată.
| |
− | |-
| |
− | |% || % ||Un caracter % urmat de alt caracter % consumă un singur % din stream.
| |
− | |}
| |
− | În afară de n, cel puțin un caracter va fi consumat din stream de către oricare alt specificator. Altfel, operația eșuează și citirea se încheie în acel punct.
| |
− |
| |
− | Specificatorul de format poate conține de asemenea și sub-specificatori: steluță (*), '''width''' și '''length''' (în această ordine), care sunt opționale și respectă următoarele specificații:
| |
− |
| |
− | {| class="wikitable"
| |
− | ! sub-specifcator !! Descriere
| |
− | |-
| |
− | | * || O steluță ca sub-specifiator indică faptul că data citită nu este stocată într-o variabilă ci este ignorată.
| |
− | |-
| |
− | | ''width'' || Specifică numărul maxim de caractere ce vor fi citite din stream de operația curentă.
| |
− | |-
| |
− | | ''length'' || Unul din hh, h, l, ll, j, z, t, L. Acest caracter modifică tipul de date așteptat ca argument. Comportamentul acestora este identic cu cel definit la capitolul [[#Scrierea pe stdout]]
| |
− | |}
| |
− |
| |
− | <div class="regula">'''<font color="red">Atenție:</font>''' Variabilele care nu sunt de tip vector sau pointer și sunt folosite în apelul funcției <code style="color: green">fscanf</code> pentru a stoca date din stream, trebuie obligatoriu precedate de operatorul &. Explicația pentru această regulă va fi prezentată în cursul legat de pointeri și adrese de memorie.</div>
| |
− |
| |
− | == Exemple ==
| |
− |
| |
− | <ol>
| |
− | <li>
| |
− | Scrieți următorul program într-un fișier numit <code style="color: blue">exemplu_stdin.c</code>
| |
− | <syntaxhighlight lang="C">
| |
− | #include <stdio.h>
| |
− |
| |
− | int main(){
| |
− | int integerValue;
| |
− | fprintf(stdout, "Introduceti o valoare numerica: ");
| |
− | fscanf(stdin, "%d", &integerValue);
| |
− | printf("Valoarea introdusa este %d\n", integerValue);
| |
− | return 0;
| |
− | }
| |
− | </syntaxhighlight>
| |
− | </li>
| |
− | </ol>
| |
− |
| |
− | = [https://en.wikipedia.org/wiki/C_preprocessor Preprocesorul C] =
| |
− |
| |
− | Preprocesarea unui fișier cu cod C este prima etapă din lanțul de ''build'', și reprezintă, așa cum îi spune și numele, o etapă de dinainte de procesarea (compilarea) efectivă. Motivul pentru care se realizează această etapă inițială este faptul că prin preprocesarea codului se pot elimina secțiuni din program care ori nu sunt necesare, ori nu sunt compilabile pe un anumit procesor sau sistem de operare. Spre exemplu, pentru a folosi o interfață serială, în sistemul de operare Windows, și compilatorul Visual Studio, există un tip de date care se numește <code style="color: green">HANDLE</code>. Acest tip de date nu există în Linux, aici fiind înlocuit simplu cu <code style="color: green">int</code>. În acest caz, compilarea unui program care folosește <code style="color: green">HANDLE</code> pe Linux va eșua cu eroare. Aici intervin directivele de preprocesare, după cum vom vedea în continuare.
| |
− |
| |
− | În acest laborator se va discuta despre:
| |
− | * <code style="color: green">#include</code>
| |
− | * <code style="color: green">#define</code>
| |
− | * <code style="color: green">#undef</code>
| |
− | * <code style="color: green">#ifdef</code>
| |
− | * <code style="color: green">#ifndef</code>
| |
− | * <code style="color: green">#else</code>
| |
− | * <code style="color: green">#endif</code>
| |
− |
| |
− | == Directiva <code style="color: green">#include</code> ==
| |
− |
| |
− | Probabil <code style="color: green">#include</code> este cea mai frecvent utilizată directivă de preprocesare, dar și cea mai ușor de înțeles. Efectiv, preprocesorul caută fișierul specificat între paranteze unghiulare sau ghilimele în lista de directoare dintr-o listă cunoscută și apoi înlocuiește directiva <code style="color: green">#include</code> cu conținutul fișierului respectiv.
| |
− |
| |
− | === Exemplu ===
| |
− |
| |
− | * Scrieți un fișier nou într-un editor de text care să conțină următorul cod:
| |
− | <syntaxhighlight lang="C">
| |
− | int main(){
| |
− | return 0;
| |
− | }
| |
− | </syntaxhighlight>
| |
− | * Salvați fișierul cu numele <code style="color: blue">test_preprocessor.c</code> în directorul <code style="color: blue">~/work/''prenume_nume''</code>
| |
− | * Scrieți un fișier <code style="color: blue">Makefile</code> cu o singură rețetă care să producă un fișier numit <code style="color: blue">processed.c</code> din fișierul de mai sus, apelând compilatorul C (gcc) cu fanionul corespunzător pentru a face doar preprocesare (vezi [[PC Laborator 1#Generarea fișierului executabil|PC Laborator 1]]).
| |
− | * Ce diferențe există între fișierul original și fișierul preprocesat?
| |
− | * Scrieți un nou fișier numit <code style="color: blue">header_file.h</code>, care să conțină următorul text:
| |
− | <syntaxhighlight lang="C">
| |
− | // This is the start of the header file
| |
− | int variable;
| |
− | // This is the end of the header file
| |
− | </syntaxhighlight>
| |
− | * Modificați fișierul <code style="color: blue">test_preprocesor.c</code> prin înserția pe prima linie a unei directive '''include''':
| |
− | <syntaxhighlight lang="C">
| |
− | #include "header_file.h"
| |
− | int main(){
| |
− | return 0;
| |
− | }
| |
− | </syntaxhighlight>
| |
− | * Rulați din nou comanda <code style="color: green">make</code>. Observați mesajul de eroare: fișierul <code style="color: blue">header_file.h</code> nu este găsit de preprocesor, cu toate că este în directorul curent.
| |
− | <div class="regula">'''<font color="red">Atenție:</font>''' Fișierele antet (header) sunt căutate doar în anumite căi predefinite în compilator (de exemplu <code style="color: blue">/usr/include</code>). Pentru a face preprocesorul să caute și în alte directoare, calea până la acestea trebuie specificată la compilare, folosind fanionul <code style="color: green">-I</code></div>
| |
− | * Adăugați comenzii de compilare din <code style="color: blue">Makefile</code> următorul fanion: <code style="color: green">-I.</code> Asta va spune compilatorului (care mai departe va spune preprocesorului) să caută fișiere antet și în directorul curent (.)
| |
− | * Rulați din nou comanda <code style="color: green">make</code>.
| |
− | * Afișați conținutul celor două fișiere C. Ce diferențe observați?
| |
− |
| |
− | == Directivele <code style="color: green">#define</code> și <code style="color: green">#undef</code> ==
| |
− |
| |
− | Directiva <code style="color: green">#define</code> este utilizată pentru a defini '''macro'''-uri de preprocesor. Acestea se folosesc în trei feluri distincte:
| |
− | * <code style="color: green">#define ''token'' ''value''</code> - este definit '''macro'''-ul ''token'' existența acestuia putând fi testată cu directivele <code style="color: green">#ifdef</code> și <code style="color: green">#ifndef</code>; dacă ''token'' este utilizat în program, după definirea lui, el va fi înlocuit cu ''value'' (efectiv, această operație este identică cu un "Search and Replace" dintr-un editor de text, unde ''token'' este înlocuit cu ''value''.
| |
− | <syntaxhighlight lang="C">
| |
− | #define PI 3.1415
| |
− |
| |
− | float a = 2 * PI;
| |
− | </syntaxhighlight>
| |
− | * <code style="color: green">#define ''token''</code> - este definit '''macro'''-ul ''token'', existența acestuia putând fi testată cu directivele <code style="color: green">#ifdef</code> și <code style="color: green">#ifndef</code>; dacă ''token'' este utilizat în program, după definirea lui, va fi șters de peste tot unde apare (este de fapt cazul de mai sus unde ''value'' este de fapt un string de lungime zero).
| |
− | <syntaxhighlight lang="C">
| |
− | #include <stdio.h>
| |
− | #define DEBUG
| |
− |
| |
− | int main(){
| |
− | #ifdef DEBUG
| |
− | printf("Debug is ON!\n");
| |
− | #endif
| |
− | return 0;
| |
− | }
| |
− |
| |
− | </syntaxhighlight>
| |
− | * <code style="color: green">#define ''token(arg1,arg2,..) expression''</code> - este definit '''macro'''-ul ''token'', existența acestuia putând fi testată cu directivele <code style="color: green">#ifdef</code> și <code style="color: green">#ifndef</code>; ''token'' este utilizat în program ca o funcție, iar el va fi înlocuit de ''expression'', în care ''arg1,arg2,...'' vor fi înlocuite cu valorile din program.
| |
− | <syntaxhighlight lang="C">
| |
− | #include <stdio.h>
| |
− | #define MAX(a,b) (a < b ? b : a)
| |
− |
| |
− | int main(){
| |
− | printf("Value is %d!\n", MAX(4,5)); // Replaced to: printf("Value is %d!\n", (4 < 5 ? 5 : 4));
| |
− | // printf("Value is %d!\n", MAX(4 + 1, 5 + 1)); // Replaced to: printf("Value is %d!\n", (4 + 1 < 5 + 1 ? 5 + 1 : 4 + 1));
| |
− | return 0;
| |
− | }
| |
− | </syntaxhighlight>
| |
− |
| |
− |
| |
− | <div class="regula">'''<font color="red">Atenție:</font>''' O greșeală frecventă în definirea macrourilor este adăugarea unui caracter ; la sfârșitul liniei. Acesta se va înlocui și el în momentul în care '''macro'''-ul se expandează în text:
| |
− | <syntaxhighlight lang="C">
| |
− | #include <stdio.h>
| |
− | #define PI 3.1415;
| |
− |
| |
− | int main(){
| |
− | float f = PI * 2; // Replaced to: float f = 3.1415; * 2;
| |
− | return 0;
| |
− | }
| |
− | </syntaxhighlight>
| |
− | Se vede imediat că sintaxa este greșită și compilatorul va genera o eroare.
| |
− | </div>
| |
− |
| |
− | Pentru a anula definiția unui '''macro''', se folosește directiva <code style="color: green">#undef</code> astfel:
| |
− | <syntaxhighlight lang="C">
| |
− | #undef PI
| |
− | #undef DEBUG
| |
− | #undef MAX
| |
− | </syntaxhighlight>
| |
− |
| |
− | === Exemplu ===
| |
− |
| |
− | * Modificați fișierul <code style="color: blue">test_preprocessor.c</code> în felul următor:
| |
− | <syntaxhighlight lang="C">
| |
− | #define PI 3.1415
| |
− | #define INC(x) (x + 1)
| |
− | #define DEBUG
| |
− |
| |
− | int main(){
| |
− | printf("PI is %f\n", PI);
| |
− | printf("%d comes after 4\n", INC(4));
| |
− |
| |
− | #ifdef DEBUG
| |
− | printf("This runs in debug mode\n");
| |
− | #else
| |
− | printf("This runs in release mode\n");
| |
− | #endif
| |
− | return 0;
| |
− | }
| |
− | </syntaxhighlight>
| |
− | * Rulați comanda <code style="color: green">make</code> în consolă.
| |
− | * Afișați cele două fișiere .c. Observați diferențele.
| |
− |
| |
− | == Directivele <code style="color: green">#ifndef</code> și <code style="color: green">#define</code> pe post de gardă pentru dublă incluziune ==
| |
− |
| |
− | Luând ca punct de plecare [[#Exemplu|primul exemplu]] de la directiva <code style="color: green">#include</code>, oare ce se întâmplă dacă header-ul <code style="color: blue">header_file.h</code> este inclus de două ori?
| |
− | <syntaxhighlight lang="C" line>
| |
− | #include "header_file.h"
| |
− | #include "header_file.h"
| |
− | int main(){
| |
− | return 0;
| |
− | }
| |
− | </syntaxhighlight>
| |
− |
| |
− | Răspunsul este simplu, preprocesorul va înlocui ambele linii cu conținutul fișierului, lucru care va face ca variabila '''variable''' să fie definită de două ori, lucru care sintactic greșit în C. Sigur, nimeni nu va include în mod voit un header de două ori, dar este posibil ca indirect lucrul acesta să se întâmple. Spre exemplu, dacă în fișierul <code style="color: blue">header_file.h</code> este inclus <code style="color: blue">stdio.h</code>, iar în <code style="color: blue">test_preprocessor.c</code> sunt incluse și <code style="color: blue">header_file.h</code> și <code style="color: blue">stdio.h</code>. În această situație, <code style="color: blue">stdio.h</code> ajunge să fie inclus de două ori. Pentru a evita problemele apărute în această situație, se folosește garda de incluziune ([https://en.wikipedia.org/wiki/Include_guard include guard]). Aceasta se adaugă în fiecare fișier header:
| |
− |
| |
− | <syntaxhighlight lang="C" line>
| |
− | #ifndef _HEADER_FILE_H_
| |
− | #define _HEADER_FILE_H_
| |
− |
| |
− | // This is the start of the header file
| |
− | int variable;
| |
− | // This is the end of the header file
| |
− |
| |
− | #endif
| |
− | </syntaxhighlight>
| |
− |
| |
− | Includerea acestui fișier de către preprocesor se realizează acum în următoarea secvență:
| |
− | # Prima directivă include din fișerul sursă C va copia conținutul fișierului de mai sus în locul unde este inclus (test_preprocesor.c, linia 1).
| |
− | # Directiva ifndef verifică dacă '''macro'''-ul _HEADER_FILE_H_ ''nu'' este definit (header_file.h, linia 1).
| |
− | # Deoarece nu este definit, tot textul dintre ifndef și endif se păstrează (header_file.h, liniile 2-7).
| |
− | # Prima linie din text-ul păstrat este o directivă define care definește '''macro'''-ul_HEADER_FILE_H (header_file.h, linia 2).
| |
− | # A doua directivă include din fișerul sursă C va copia conținutul fișierului de mai sus în locul unde este inclus (test_preprocesor.c, linia 2).
| |
− | # Directiva ifndef verifică dacă '''macro'''-ul _HEADER_FILE_H_ ''nu'' este definit (header_file.h, linia 1).
| |
− | # Deoarece el acum ESTE definit în cadrul include-ului anterior, tot textul dintre ifndef și endif se elimină (header_file.h, liniile 2-7).
| |
− |
| |
− | === Exemplu ===
| |
− |
| |
− | * Modificați fișierul <code style="color: blue">test_preprocessor.c</code> pentru a include header-ul <code style="color: blue">header_file.h</code> de două ori, conform exemplului de mai sus.
| |
− | * Rulați comanda <code style="color: green">make</code> și confirmați dubla declarare a variabilei '''variable'''.
| |
− | * Adăugați gardă de incluziune fișierului header, conform exemplului de mai sus.
| |
− | * Rulați comanda <code style="color: green">make</code> și confirmați că variabile '''variable''' este declarată o singură dată.
| |