Sunteți pe pagina 1din 10

Modulul 0.

Alocare dinamica in limbajul C

Capitolul 0. Pointeri si alocare dinamica. Tipul de date struct

0.1 Pointeri si alocare dinamica

O problema care apare in momentul declararii unui sir era aceea ca


dimensiunea sirului trebuia sa fie constanta, iar daca doream sa rulam programul
pentru siruri de dimensiuni diferite atunci trebuia sa definim o dimensiune suficient de
mare a sirului si in program sa folosim numai un numar de elemente ale sirului, numar
introdus de utilizator la fiecare rulare a programului, celelalte elemente nefiind
folosite si deci ocupand inutil un spatiu de memorie. Pentru rezolvarea acestei
probleme exista o modalitate de alocare a memoriei ce se numeste alocare dinamica,
adica alocare a memoriei in momentul rularii, permitand programului sa isi ajusteze
capacitatea de memorare a datelor in functie de necesitatile utilizatorului.
In C sunt disponibile patru functii care pot aloca atat spatiu cat este dorit, pot
modifica dimensiunea unui sir existent sau pot elibera zone de memorie alocate
anterior. Aceste functii sunt:
 malloc
 calloc
 realloc
 free
Toate apartin bibliotecii stdlib.h.
Prototipul functiei malloc este:
void *malloc(size_t dimensiune);
malloc rezerva un numar de octeti specificat de catre parametrul dimensiune, in locatii
de memorie consecutive returnand un pointer la primul octet sau pointerul NULL
(adica pointerul 0, care nu puncteaza nimic) daca nu poate aloca memoria solicitata.
Functia returneaza un pointer de tip void adica in momentul in care acest pointer este
atribuit unui pointer de un anumit tip atunci valoarea pointerului returnat de catre
functia malloc se va transforma in tipul corespunzator. size_t corespunde tipului
intreg fara semn, fiind tipul rezultatului pe care il returneaza operatorul sizeof. Acest
tip este definit in stddef.h.
Exemplu:
int *p;
p = malloc(5 * sizeof (int) );
malloc va aloca 5*4=20 de octeti (am presupus ca dimensiunea int este de patru
octeti) va returna un pointer la primul octet si este transformat in pointer de tip int
memorat in pointerul p de tip int.
Deci p este de fapt un pointer la un sir de 5 variabile int. Putem initializa elementele
sirului, putem efectua orice prelucrare a elementelor sirului ca si cum ele ar fi fost
alocate in momentul alocarii.
Prototipul functiei calloc este:
void *calloc(size_t n, size_t dimensiune);
Functia calloc aloca spatiu de memorare pentru n elemente, fiecare de marimea
dimensiune. Fiecare element este initializat cu zero. Functia returneaza un pointer la

1
spatiul alocat si NULL daca nu este spatiu suficient in memorie sau daca n = 0 sau
dimensiune = 0.
Exemplu:
float *p;
p = calloc(10, sizeof(float) );
Prototipul functiei realloc este:
void *realloc(void *p, size_t dimensiune);
Functia realloc modifica marimea unui spatiu pentru date deja existente. p este un
pointer catre o zona de memorie deja alocata. Daca p este NULL functia realloc
functioneaza ca si malloc, alocand un bloc de atatia octeti cati sunt precizati de
dimensiune. Daca p nu este NULL atunci p trebuie sa fie un pointer returnat de un
apel anterior al uneia din functiile malloc, calloc sau realloc. Parametrul dimensiune
determina noua marime in octeti a blocului de memorie alocat. Acest proces are loc
fara sa se stearga datele existente. Daca zona de memorie existenta nu poate fi extinsa
fara sa se suprapuna cu alte date, intreaga zona este copiata la o alta locatie iar zona
veche este eliberata. Functia returneaza un pointer la noua zona de memorie alocata
sau NULL.
Exemplu:
alocdinam.C

#include<stdio.h>
#include<stdlib.h>
main()
{
char *pc;
pc = malloc(50 * sizeof(char));
if(pc == NULL)
printf("Insuficient spatiu");
else
{
printf("Bloc de 50 de caractere alocat cu succes \n");
realloc(pc , 100 * sizeof(char));
if(pc == NULL) printf("Insucces realloc");
else printf("Bloc de 100 de caractere alocat cu succes\n");
}
}

Prototipul functiei free este:

void free(void *p);

Functia free elibereaza blocul de memorie indicat de pointerul p, unde p trebuie sa fie
un pointer returnat de un apel anterior al uneia din functiile malloc, calloc sau realloc.
Functia free nu poate fi utilizata pentru eliberarea memoriei alocate de variabile in
momentul compilarii. Dupa apelarea functiei free blocul eliberat este disponibil
pentru alocare. Un pointer NULL ca argument este ignorat.

2
Exemplu:
free.C
#include<stdio.h>
#include<stdlib.h>
main( )
{
char *pc, *p, c;
int i;
pc = (char *) malloc(32*sizeof(char));
p = pc;

for(c = 'A'; c <= 'Z'; c ++)


{
*p = c;
p++;
}
for(i = 0; i < 32; i++)
printf("%c ", *(pc + i));
puts("");
free(pc);
for(i = 0; i < 32; i++)
printf("%c ", *(pc + i));
}

0.2 Tipul struct

De multe ori dorim sa grupam mai multe date de tipuri diferite sau de acelasi
tip sub acelasi nume deoarece din punct de vedere logic ele reprezinta aceeasi entitate.
De exemplu, un punct in plan este reprezentat prin doua numere reale, numere care de
fapt descriu o singura entitate. Deci, un program va fi mai usor de inteles si de scris,
daca pentru descrierea punctului, in acel program, definim o singura entitate ce
grupeaza doua variabile de tip double decat daca lucram in program cu doua entitati
separate de tip double corespunzatoare coordonatelor carteziene ale punctului. Un alt
exemplu: In general, pentru afisarea notelor studentilor ne sunt necesare urmatoarele
informatii despre fiecare student: numele, prenumele si notele la examene. Putem
grupa cele trei caracteristici (doua de tip sir de caractere si unul de tip sir de intregi)
sub acelasi nume, ele reprezentand, in ansamblu, aceeasi entitate.
Gruparea mai multor elemente se face, in C, prin definirea unui nou tip de
date, un tip de date care este definit de programator (deci nu este predefinit) si care se
numeste tipul struct. Descrierea unui punct in plan se va face atunci in felul urmator:
struct punct
{
double x;
double y;
};

3
iar o variabila de acest tip va putea fi declarata
struct punct p1;
si va avea de fapt doua componente la care se poate face referire in felul urmator:
coordonata x va fi p1.x iar coordonata y va fi p1.y. Dar sa vedem cum se defineste
acest tip struct in general:
struct nume_tip
{
tip_membru1 var_membru1;
tip_membru2 var_membru2;
...
nu uitati ;
};
in care struct este cuvant cheie (keyword), nume_tip este numele pe care
programatorul il da tipului de date nou creat, tip_membru1 var_membru1;
tip_membru2 var_membru2; ... sunt declaratiile variabilelor componentelor tipului de
date, cele care sunt grupate impreuna si sunt tratate ca si o singura entitate.
var_membru1, var_membru2, ... se numesc numele membrilor tipului de date.
Exemplu:
struct info_student
{
char *nume;
char *prenume;
int note[10];
};
descrie un nou tip struct pentru reprezentarea informatiei unui student.
Declararea variabilelor de tipul nou creat (de tip struct), variabile care se mai
numesc si structuri, se poate face in felul urmator:
struct nume_tip nume_var_structura;
si deci o variabila de tip info_student poate fi declarata:
struct info_student student;
nume_tip este optional deci putem defini tipuri struct fara nume. In acest caz
declararea variabilelor se va face in momentul definirii tipului struct, ca de exemplu:
struct
{
double x;
double y;
double z;
} origine, pct;
In acest caz origine si pct sunt doua variabile de tip structura.
Putem declara variabile de tip structura in acelasi timp cu definirea tipului de
date chiar si atunci cand se precizeaza numele tipului:
struct nume_tip
{
tip_membru1 var_membru1;
tip_membru2 var_membru2;
...
} nume_var1, nume_var2;
Definitia unui tip struct este de obicei plasata in afara oricarei functii (ca si
declararea variabilelor globale), caz in care definitia este globala si va fi valabila in
fragmentul de program ce urmeaza definitiei.

4
Dupa declararea variabilei de tip struct, variabilele membru ale acesteia se pot
accesa in felul urmator:
nume_var_structura.var_membru
unde . este operatorul punct ce are precedenta cea mai mare si se asociaza de la stanga
la dreapta. Aceste variabile reprezinta si se comporta ca orice alte variabile de tipul
tip_membru corespunzator.
Exemple: Cu definitiile de mai sus, putem scrie
student.nume si are tipul char*
origine.x si are tipul double
pct.y si are tipul double
student.note si are tipul sir de 10 intregi. Elementele sirului pot fi referite in
felul urmator: student.note[0], student.note[1], ..., student.note[9].
De asemenea, putem tipari coordonatele punctului p1 (variabila de tip struct punct):
printf("(%6.2f,%6.2f)", p1.x, p1.y);
sau initializa variabila origine de tipul struct fara nume:
origine.x = 0.0;
origine.y = 0.0;
origine.z = 0.0;
sau putem prelucra informatia despre numele unui student (folosind variabila student
de tip info_student):
char s[50];
strcpy(s, student.nume);
strcat(s," ");
strcat(s, student.prenume);
puts(s);
care va tipari numele studentului urmat de un spatiu si de prenume.
Numele unui membru al unei structuri precum si numele unui tip struct pot fi
aceleasi cu numele unei variabile simple deoarece nu exista posibilitatea unui conflict,
al unei confuzii. Deci putem avea, in acelasi program, declaratiile de mai sus alaturi
de declaratiile:
char *nume;
int x;
int *z;
double punct;
Mai mult, putem defini doua structuri diferite cu unul sau mai multe nume de membri
comune. Sunt permise ambele definitii

struct struct
{ {
int a; char *a;
double b; int x;
} str1; } structura1;

deoarece cei doi membri vor fi accesati numai prin intermediul variabilelor carora
apartin: str1.a, si respectiv, structura1.a.
Structurile pot fi initializate in momentul in care sunt declarate, prin folosirea
unei liste de valori initiale, cate o valoare pentru fiecare membru in ordinea aparitiei
in lista care defineste tipul struct.
Exemplu:
struct punct p = {0, 0}, q = {1.5, 2.5};

5
dar valorile membrilor structurii nu pot fi modificate mai tarziu in program cu
instructiunea:
p = {3, 2}; incorec
t
ci va trebui sa folosim cate o instructiune de atribuire pentru fiecare variabila
membru:
p.x = 3; p.y = 2;
Putem copia o structura in alta structura (daca sunt de acelasi tip):
p = q;
in schimb nu putem compara doua structuri cu operatorul ==:
p == q
incorec
t
O declaratie de tip struct care nu este urmata de o lista de variabile (deci este
definit numai tipul de date), nu aloca nici un spatiu de memorie. Acest tip de
declaratie descrie numai forma (sablonul) unei structuri. Alocarea memoriei se va face
abia cand se vor defini variabilele de tip struct. Pentru a afla cat spatiu de memorie
este necesar pentru alocarea unei variabile de acel tip, putem folosi operatorul sizeof.
In orice caz, numarul de octeti necesari unui tip struct va fi cel putin egal cu suma
numarului de octeti alocati fiecarui membru al structurii.
Exemplu:
sizeof(struct info_student) va arata 16 (octeti)
sizeof(struct punct) va arata 48 (octeti)
Membrii unei structuri pot avea orice tip, inclusiv un tip struct. De exemplu,
putem reprezenta un dreptunghi pe ecranul monitorului precizand coltul din stanga
sus, lungimea si latimea:
struct dreptunghi
{
struct punct sus;
double lungime;
double latime;
} d1;
Accesarea componentelor structurii d1 se face:
d1. sus de tipul punct si deci avand doua componente (d1.sus).x si (d1.sus).y
(aici parantezele se pot elimina deoarece operatorul . se asociaza de la stanga la
dreapta),
d1.lungime de tip double
d1.latime de tip double.
Observatie: Nu putem declara un membru cu acelasi tip ca si tipul struct caruia ii
apartine, dar putem declara un membru ca pointer la variabile de tipul struct caruia ii
apartine. In acest caz tipul struct trebuie sa aiba nume. (In acest fel se pot crea listele
inlantuite).
Exemplu:
struct nod
{
int x;
struct nod *next;
};
Variabilele de tip structura pot fi argumentele unei functii sau putem scrie
functii care returneaza structuri.

6
Exemplu: Putem defini o structura pentru reprezentarea unei zile calendaristice:
struct data
{
int zi;
int luna;
int an;
};
si apoi, date fiind doua zile calendaristice, putem scrie o functie care returneaza 1
daca prima zi este inaintea celei de a doua, 0 daca sunt identice si –1 daca a doua zi
este inaintea primei. Prezentam aici intregul program. Programul citeste de la tastatura
informatiile celor doua zile calendaristice. Programul contine, pe langa functia mai
sus amintita, o functie de citire si una de scriere. Functia de citire este o functie care
returneaza o variabila de tip struct data, iar functia de scriere are ca argument o
variabila de tip struct data.
struct_data.C
#include <stdio.h>
struct data
{
int zi;
int luna;
int an;
};

struct data citeste();


void scrie (struct data d);
int mai_inainte(struct data ziua1, struct data ziua2);
int semn(int n);

main()
{
int rezultat;
char c;
struct data ziua1, ziua2;
puts("Programul urmator compara doua zile calendaristice");
do
{
ziua1 = citeste();
ziua2 = citeste();

rezultat = mai_inainte(ziua1, ziua2) ;


scrie (ziua1);
if( rezultat == 1 )
printf(" este inaintea ");
else if( rezultat ==0 )
printf(" este identica cu ");
else if( rezultat == -1)
printf (" este dupa ");
else printf(" eroare ");
scrie (ziua2);
puts("");
fflush(stdin);
puts("Doriti repetarea acestui program? (Y/N)");
c = getchar(); 7
} while (c=='Y' || c=='y');
}
struct data citeste()
{
int zi, luna, an;
char c1, c2;
struct data temp;
puts("Introduceti elementele zilei calendaristice in formatul \
zi/luna/an");
scanf("%d%c%d%c%d", &zi, &c1, &luna, &c2, &an);

temp.zi = zi;
temp.luna = luna;
temp.an = an;
return temp;
}

void scrie (struct data d)


{
printf("%2d/%2d/%4d", d.zi, d.luna, d.an);
}

int semn(int n)
{
if (n > 0) return 1;
else if (n < 0 ) return -1;
else return 0;
}

int mai_inainte(struct data ziua1, struct data ziua2)


{
int temp1, temp2;
temp1 = semn(ziua2.an - ziua1.an);
if(temp1) return temp1;
else
{
temp2 = semn(ziua2.luna - ziua1.luna);
if(temp2) return temp2;
else return semn(ziua2.zi - ziua1.zi);
}
}
In general, cand o functie are drept parametru o structura este mai eficient sa fie
transmis un pointer la structura decat structura insasi, deoarece structura ar fi
transmisa prin valoare si deci ar fi necesare blocuri de memorie pentru doua structuri,
unul in functia apelanta si altul in functia apelata. Pointerii la structuri se definesc ca
pointerii la orice alt tip de variabile: struct nume_tip *p;
Exemplu: struct data *pzi; defineste un pointer pzi la variabile de tip struct data, pzi
este structura de tip data, iar componentele structurii sunt (*pzi).zi, (*pzi).luna si

8
(*pzi).an. Parantezele sunt necesare deoarece precedenta operatorului . este mai mare
decat a operatorului unar *.
Mai exista si o alta alternativa pentru accessul la componentele unei structuri pointate
de un pointer, si anume folosirea operatorului ->: daca avem declaratia struct
nume_tip p; atunci (*p).membru face accessul la o componenta a structurii si este
echivalent cu p ->membru. Pentru exemplul de mai sus, pzi -> zi, pzi -> luna, pzi ->
an sunt componentele structurii pointate de pzi.
Daca se doreste folosirea pointerilor la structuri, programul de mai sus
struct_data.C ar trebui modificat in felul urmator:
 se modifica functia scrie:
void scrie (struct data *d)
{
printf("%2d/%2d/%4d", d->zi, d->luna, d->an);
}
 se modifica functia mai_inainte:
int mai_inainte(struct data *ziua1, struct data *ziua2)
{
int temp1, temp2;
temp1 = semn(ziua2->an - ziua1->an);
if(temp1) return temp1;
else
{
temp2 = semn(ziua2->luna - ziua1->luna);
if(temp2) return temp2;
else return semn(ziua2->zi - ziua1->zi);
}
}
 se modifica apelul functiilor de mai sus in functia main:
rezultat = mai_inainte(&ziua1, &ziua2) ;
scrie (&ziua1);
scrie (&ziua2);
De asemenea, limbajul C permite redenumirea unor tipuri sau numirea unor tipuri
predefinite sau create de programator. De exemplu, daca nu ne place sa folosim int ca
nume pentru tipul de date intregi, putem redenumi acest tip de exemplu Intreg, fara ca
sa existe modificari de tipuri sau de date, in felul urmator:
typedef int Intreg;
si daca vrem sa declaram variabile o facem in felul urmator:
intreg i, n; care este echivalent cu int i, n;
Typedef nu introduce tipuri noi ci introduce doar nume noi, sinonime pentru tipuri
existente. Putem da si urmatoarea definitie
typedef char * String;
insemnand ca String este numele pe care il dam tipului pointer la caractere. Numele
nou dat unui tip poate fi orice identificator permis in C, dar conventional se folosesc
siruri de caractere alfanumerice ce incep cu litera mare.
Sintaxa declaratiei typedef este:
typedef declaratia_tipului sinonim;
Typedef este foarte folositor atunci cand tipul de date este complicat si se doreste o
versiune mult mai simplificata, nu in context ci doar in scriere. Cu declaratia
typedef struct punct
{

9
double x;
double y;
} Pct;
vom putea defini variabile de tipul struct punct mult mai simplu: Pct origine; care este
echivalent cu struct punct origine;. Declaratia typedef Pct Triunghi[3]; da numele
Triunghi tipului de date: sir de 3 elemente de tip Pct, iar Triunghi ABC; este
echivalent cu struct punct ABC[3];. Declaratia typedef struct punct TRIUNGHI[3]; da
numele TRIUNGHI tipului de date: sir de 3 elemente de tip struct punct, iar
TRIUNGHI abc; este echivalent cu struct punct abc[3];

1. Folosind alocare dinamica, creati un sir de intregi de


Lab 0
dimensiune ce este introdusa de la tastatura si apoi
00-
folosind programarea structurata (folosind functii pentru
101
executarea a partilor importante), sortati sirul, tipariti
elementele in noua ordine si eliberati memoria alocata
acestui sir.
2. Folosind tipul struct, dati o definitie pentru un cont
bancar ce contine informatiile: nr de cont, balanta, suma
minima in cont. Scrieti un program care simuleaza o
operatiune bancara: extragere sau depunere de bani in
cont. Se vor afisa informatiile contului inainte si dupa
efectuarea operatiunii.

10