Cu ajutorul cuvantului rezervat struct
putem construi un tip de date complex pornind de la tipuri de date existente. O structura este alcatuita in C printr-o colectie de asa numite campuri (date). In C, ca membrii unei structuri nu putem avea functii (asa cum se intampla in C++).
struct nume
{
tip_1 camp_11, camp_12,...;
tip_2 camp_21, camp_22,...;
...
tip_n camp_n1, camp_n2,...;
};
Declararea unei structuri se incheie obligatoriu cu ;
. Tipul de date construit in C poarta denumirea de struct nume
.
Declaratii de variabile: struct nume var1,var2,...;
In C, pentru a "scapa" de cuvantul rezervat struct se foloseste un
typedef struct nume nume;
nume var1,var2,...;
Pentru a simplifica scrierea de mai sus de multe ori se procedeaza astfel:
typedef struct
{
...
} nume;
nume Var1,Var2,...;
Intre acolada inchisa si punct si virgula pot aparea declaratii de variabile optionale.
Def: Un pointer este o variabila care poate retine o adresa de memorie.
Declaratia: tipdate* p;
Despre variabila p
se spune ca este de tipul tipdate*
sau este un pointer catre tipdate
, unde tipdate
este un tip de date existent.
ex:
int* p; //pointer catre int
int v=1;
p=&v;
&
este un operator unar ce se aplica unei variabile si returneaza adresa de memorie a variabilei respective. O adresa de memorie este de fapt un numar intreg pozitiv si care reprezinta o pozitie in memorie relativ la inceputul acesteia.
Pentru operatorul &
exista operatorul *
care este inversul operatorului &
ca functionalitate. Operatorul *
este tot un operator unar, dar care se aplica numai unui pointer. Prin *p
se intelege valoarea de la adresa retinuta in variabila p
.
In exemplul nostru, p
fiind pointer catre int
, *p
va fi o valoare de tip int
.
*p==1
*&v==v
&*p==p
*&p==p
&*v
nu este corect pentru ca v
nu este de tip pointer
Un pointer, din cauza ca retine practic valori numerice intregi inseamna ca au sens operatorii +
-
++
--
aplicati unui pointer.
p+1
= reprezinta adresa locatiei de memorie aflata imediat dupa locatia de la adresa p
. Practic, p+1
este egal cu valoarea lui p
la care se adauga sizeof(int)
, practic, p+1
este cu 4
unitati mai mare decat p
.
ex.:
int v=1;
int* p=&v;
char* p2;
p2=p;
Atribuirea p2=p
de mai sus este posibila deoarece atat p
cat si p2
sunt variabile pregatite pentru a retine adresa de memorie.
In C, atribuirea p2=p
este considerata valida. In C++, o astfel de atribuire conduce la eroare de compilare.
In C++ este corect, dar si in C: p2=(char*)p;
int v=...;
char* p=(char*)&v;
*p==R
*(p+1)=G
*(p+2)=B
*(p+3)=α
struct test
{
int i;
float f;
};
struct test t;
scanf("%d%f",&t.i,&t.f);
printf("%d %f",t.i,t.f);
printf("%d %f",*(int*)t);
*(float*)(int*)=++1;
struct t
{
short s;
char c;
};
int a[10]; //a este o variabila ce retine adresa primei locatii de memorie
//din vectorul de lungime 10. Din acest punct de vedere, a se aseamana
//cu un pointer. Deosebirea este ca a este o constanta care retine
//o adresa de memorie, deci, a nu isi poate modifica valoarea.
a=7; //nu este corect
a++; //nu este corect
10
casute de tip int
*a;
*(a+1);
In C, pentru tipul pointer exista declarat operatorul binar []
. Prin a[k]
compilatorul intelege si traduce in *(a+k)
. Operatorul []
este introdus pentru a lucra cu vectori in C mai aproape de limbajul natural, dar scrierea a[k]
este tradusa la compilare imediat in *(a+k)
, echivelent cu *(k+a)
, deci echivalent cu k[a];
1[a];
a doua casuta din vectorul a
In C, pe langa pointeri catre variabile putem avea pointeri catre functii. Practic, un astfel de pointer retine adresa de memorie unde este implementata o functie. ex:
int inc (int i)
{
return i+1;
}
void main()
{
int (*p)(int);
p=&inc;
printf("%d\n",(*p)(7)); //=> 8
}
In momentul in care un pointer catre o functie primeste adresa unei functii, compilatorul accepta si scrierea p=inc;
. Apelul unei functii a carei adresa este retinuta intr-un pointer poate fi facut si fara a mai folosi operatorul *
.
ex:p(7)
Din cauza ca in C putem aveam pointer catre functii, putem aduce astfel in interiorul unui struct si functii, scriind practic pointer catre functii pe care ii initializam cu adresele functiilor dorite.
ex:
struct t
{
int i;
int (*f)(int);
};
void inc(int i)
{
i++;
}
void main()
{
int x=1;
inc(x);
printf("%d",x); //=>1
}
In C exista numai transmitere de parametri prin valoare, adica practic parametri functiei in momentul apelului se initializeaza cu valorile de apel.
Pentru exemplul de mai sus, in stiva de executie a programului avem:
x
intra in stiva de executie la intalnirea declaratieix
x
se initializeaza cu1
- la apelul functiei
inc
se intalneste declaratia variabileii
, ca fiind de tipint
care intra in stiva de executie si primeste valoarea de apel, adica valoarea luix
din momentul apelului (i
devine1
). - se excuta functia
inc
.i
creste cu o unitate,i
devine2
- la parasirea functiei
inc
, variabilai
este scoasa din stiva de executie - executia continua in
main
, dupa apelulinc()
, se afiseaza valoarea luix
, care a ramas, evident,1
. - la parasirea
main
, variabilax
paraseste stiva de executie.
Pentru a returna valori calculate de o functie prin intermediul parametrilor, in C se face un artificiu, in sensul ca, in loc sa transmitem in aceasta situatie valoarea unei variabile, transmitem adresa ei.
int inc2 (int* i)
{
(*i)++;
}
void main()
{
int x=1;
inc2(&x);
printf("%d\n",x);
}
Stiva de executie:
- intra variabila
x
x
ia valoarea1
- la apelul functiei
inc2
, in stiva de executie intra variabilai
, care este pointer si se initializeaza cu valoarea adresei luix
; scrierea(*i)++
este corecta pentru ca*i == *&x == x
- variabila
i
paraseste stiva de executie - se afiseaza
2
ex:
void div(int x, int y, int* c, int* r)
{
*c = x/y;
*r = x%y;
}
void main()
{
int a,b;
scanf("%d%d",&a,&b);
div(a,b,&c,&r);
printf("%d %d",c,r);
}
In scrierea int a[10]
avem alocat static 10
casute de tip int
pentru variabila a
. Intre paranteze []
trebuie neaparat sa apara o constanta. La alocarea statica trebuie sa cunoasteam, inca din momentul redactarii codului care este lungimea maxima a zonei de memorie pe care o vom folosi, in exemplul de mai sus, pentru vectorul a
.
In momentul rularii aplicatiei, de cele mai multe ori, din contextul executiei programului se poate determina lungimea zonei de memorie necesara executiei. De aceea, de cele mai multe ori se prefera ca pentru vectori, matrice, etc. sa se aloce dinamic memorie in momentul executiei aplicatiei. Avantaje:
- putem aloca exact atata memorie cat avem nevoie
- putem elibera oricand zonele de memorie de care nu mai avem nevoie
- dinamic, putem aloca memorie mult mai mult decat static.
Pentru a aloca dinamic in C se folosesc urmatoarele functii: malloc
,calloc
, realloc
, free
Functiile de mai sus sunt in stdlib.h
si malloc.h
malloc(int)
- nr de octeti de memorie ce se doreste sa fie alocat. Functia returneaza un pointer catre void
. In cazul in care alocarea s-a facut cu succes, se returneaza adresa de memorie a zonei alocatie dinamic. In caz de insucces, se returneaza NULL
.
ex:
int* x=(int*)malloc(sizeof(int));
int* a=(int*)malloc(n*sizeof(int));
if(a==NULL) exit(1);
a
retine adresa de inceput a vectorului
constanta NULL
are valoarea 0
Un pointer care are valoarea NULL
inseamna ca nu retine nicio adresa de memorie.
calloc
primeste 2 param : primul reprezinta nr de blocuri de memorie ce se doreste
calloc
se potriveste pentru alocare de mamorie pt un vector
Spre deosebire de malloc
, functia calloc
initilizeaaza toti octetii de memorie zonei de memorie alocate dinamic cu valoarea 0
. In consecinta un apel calloc
este mai lent decat un apel malloc
echivalent.
realloc(void*, int)
- functia realloc
realoca memoria de la adresa primita in primul parametru la o alta adresa. Noua locatie de memorie va avea lungimea in octeti specificata de al doilea parametru. Daca alocarea este cu succes pentru noua locatie, informatia de la adresa din primul parametru este copiata la noua adresa.
int n, *a;
...
n=(int*)realloc(n*sizeof(int));
...
a=2;
a=(int*)realloc(a,n*sizeof*(int));
Daca lungimea noii zone de memorie este mai mica, atunci de la adresa initiala se copiaza exact atatia octeti cati am precizat in al doilea parametru.
free(void*)
- elibereaza zona de memorie alocata dinamic de la adresa primita ca parametru
cu free
putem dealoca doar o zona de memorie alocata cu succes al uneia dintre functiile malloc
, calloc
sau realloc
.
Daca se incearca sa se aloca memorie de valoare negativa, fucntia returneaza valoarea NULL
. Daca se incearca a se aloca 0
octeti, in functie de compilator, functia va returna adresa NULL
sau o adresa de memorie unde evident nu avem casute.
ex:
int a[10];
int n=10;
int *b=(int*)malloc(n*sizeof(int));
printf("%d %d",sizeof(a),sizeof(b)); //=> 40 4
Variabila a
retine adresa de memorie a unei zone de lungime 40
de octeti in stiva de executie, iar b
este un pointer catre int
si orice pointer se reprezinta pe 4
octeti.
In momentul in care alocam dinamic memorie si o zona astfel alocata nu este eliberata spunem ca avem "memory leak"
Dezavantajele memory leak:
- zonele alocate dinamic si neeliberate pe parcursul executiei aplicatiei pot creste ca numar si ca dimensiune, existand riscul, ca executia aplicatiei sa nu se mai poata executa, ne mai existand memorie pentru a fi alocata dinamic.
- prin acumularea zonelor neeliberate, calculatorului i se va ingreuna functionalitatea
- la inchiderea unei aplicatii cu memory leak, OS va identifica si verifica zonele de memorie alocate dinamic neeliberate pe parcursul executiei, proces care este lent sau extrem de lent.