PC Laborator 2.1 (opțional)
Obiective
În urma parcurgerii acestui laborator studentul va fi capabil:
- să înțeleagă funcționalitatea preprocesorului și să folosească directive de preprocesare;
- 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.
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 HANDLE
. Acest tip de date nu există în Linux, aici fiind înlocuit simplu cu int
. În acest caz, compilarea unui program care folosește HANDLE
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:
#include
#define
#undef
#ifdef
#ifndef
#else
#endif
Directiva #include
Probabil #include 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 #include 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:
int main(){
return 0;
}
- Salvați fișierul cu numele
test_preprocessor.c
în directorul~/work/prenume_nume
- Scrieți un fișier
Makefile
cu o singură rețetă care să producă un fișier numitprocessed.c
din fișierul de mai sus, apelând compilatorul C (gcc) cu fanionul corespunzător pentru a face doar preprocesare (vezi PC Laborator 1). - Ce diferențe există între fișierul original și fișierul preprocesat?
- Scrieți un nou fișier numit
header_file.h
, care să conțină următorul text:
// This is the start of the header file
int variable;
// This is the end of the header file
- Modificați fișierul
test_preprocesor.c
prin înserția pe prima linie a unei directive include:
#include "header_file.h"
int main(){
return 0;
}
- Rulați din nou comanda
make
. Observați mesajul de eroare: fișierulheader_file.h
nu este găsit de preprocesor, cu toate că este în directorul curent.
/usr/include
). Pentru a face preprocesorul să caute și în alte directoare, calea până la acestea trebuie specificată la compilare, folosind fanionul -I
- Adăugați comenzii de compilare din
Makefile
următorul fanion:-I.
Asta va spune compilatorului (care mai departe va spune preprocesorului) să caută fișiere antet și în directorul curent (.) - Rulați din nou comanda
make
. - Afișați conținutul celor două fișiere C. Ce diferențe observați?
Directivele #define
și #undef
Directiva #define
este utilizată pentru a defini macro-uri de preprocesor. Acestea se folosesc în trei feluri distincte:
#define token value
- este definit macro-ul token existența acestuia putând fi testată cu directivele#ifdef
și#ifndef
; 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.
#define PI 3.1415
float a = 2 * PI;
#define token
- este definit macro-ul token, existența acestuia putând fi testată cu directivele#ifdef
și#ifndef
; 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).
#include <stdio.h>
#define DEBUG
int main(){
#ifdef DEBUG
printf("Debug is ON!\n");
#endif
return 0;
}
#define token(arg1,arg2,..) expression
- este definit macro-ul token, existența acestuia putând fi testată cu directivele#ifdef
și#ifndef
; 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.
#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;
}
#include <stdio.h>
#define PI 3.1415;
int main(){
float f = PI * 2; // Replaced to: float f = 3.1415; * 2;
return 0;
}
Se vede imediat că sintaxa este greșită și compilatorul va genera o eroare.
Pentru a anula definiția unui macro, se folosește directiva #undef
:
#undef PI
#undef DEBUG
#undef MAX
Exemplu
- Modificați fișierul
test_preprocessor.c
în felul următor:
#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;
}
- Rulați comanda
make
în consolă. - Afișați cele două fișiere .c. Observați diferențele.