Sunteți pe pagina 1din 10

Curs 6.

POINTERI
Un pointer este o variabil care conine o adres de memorie.
Pointerii sunt foarte mult utilizai n C pe de o parte pentru c uneori sunt singura cale de rezolvare a unei anumite
probleme, iar pe de alt parte pentru c folosirea lor duce la alctuirea unui cod mai compact i mai eficient.
Ca metod, pointerii se utilizeaz pentru un plus de simplitate.

6.1. Pointeri i adrese


Un pointer fiind o variabil ce conine o adres de memorie, in particular, ea poate referi adresa unei variabile din program.
Deci este posibil adresarea acestei variabile "indirect" prin intermediul pointerului.
Variabilele de tip pointer se declar prin construcii de forma:
tip *nume;
Fie x o variabil, de tipint i px este un pointer la acea variabil.
Operatorul & d adresa unei variabile, astfel nct instruciunea :
px=&x
d variabilei px adresa lui x, px nseamn "pointeaz pe x". Operatorul & poate fi aplicat numai variabilelor i elementelor
unui tablou.
Invers, dac avem un pointer px, prin *px se face referire la valoarea care se gseste memorat la adresa pointat de px.
Exemple.
int *p, n=5, m;
p=&n;
m=*p;
m=*p+1;

//declarm p ca pointer la ntreg i n i m ntregi


//p va conine adresa variabilei n deci p va pointa (indica) spre n
//lui m i se d valoarea care se gsete la adresa indicat de p
//deci m va conine valoarea lui n; n consecin m=5
//m va conine valoarea 6

Construcii ca &(x+1) i &3 sunt interzise. Este de asemenea interzis pstrarea adresei unei variabile registru.
Operatorul unar * trateaz operandul su ca o adres, acceseaz aceast adres i i obine coninutul.
Astfel, dac y este tot un int
y = *px
asigneaz lui y, ori de cte ori este cazul, coninutul locaiei unde pointeaz px.
Astfel secvena
px = &x;
y = *px;
asigneaz lui y aceiai valoare ca i
y = x
Totodat este necesar declararea variabilelor care apar n secvena:
int x, y;
int *px;

Declararea lui x i y este deja cunoscut. *px este un int, adic n momentul n care px apare n context sub forma *px,
este echivalent cu a ntlni o variabil de tip int. De fapt, sintaxa declararii unei variabile imit sintaxa expresiilor n care
ar putea s apar respectiva variabil. Acest raionament este util n toate cazurile care implic declaraii complicate.
Exemple:
double atof(), *dp; //atof() i *dp au valoare de tip double.
De notat declaraia implicit, ceea ce vrea s nsemne c un pointer este constrns s pointeze o anumit categorie de
obiecte (funcie de tipul obiectului pointat).
Pointerii pot apare n expresii.
De exemplu, dac px pointeaz pe ntregul x atunci *px poate apare n orice context n care ar putea apare x.
y = *px + 1
d lui y o valoare egal cu x plus 1.
printf("%d\n", *px)
imprim o valoare curent a lui x i d = sqrt((double) *px)face ca d = radical din x, care este forat de tipul
double nainte de a fi transmis lui sqrt.
n expresii ca
y = *px + 1
operatorii unari * i & au prioritate mai mare dect cei aritmetici, astfel aceast expresie, ori de cte ori pointerul px
avanseaz, adun 1 i asigneaz valoarea lui y.
Referiri prin pointer pot apare i n partea stng a asignrilor. Daca px pointeaz pe x atunci
*px = 0
l pune pe x pe zero i
*px += 1
l incrementeaz pe x, ca i
(*px)++
In acest ultim exemplu parantezele sunt necesare; fr ele, expresia va incrementa pe px n loc s incrementeze ceea ce
pointeaz px deoarece operatorii unari * i + sunt evaluai de la dreapta la stnga.
Dac pointerii sunt variabile, ei pot fi manipulai ca orice alt variabil. Daca py este un alt pointer pe int, atunci
py = px
copiaz coninutul lui px n py fcnd astfel ca py s se modifice odat cu px.
O mare atenie trebuie acordat tipului variabilei spre care pointeaz un pointer. Urmtorul exemplu este sugestiv n acest
sens:
int *p;
double x=1.23, y;
p=&x;
y=*p;
//valoarea lui y va fi total eronat datorit tipului pointerului p
//p este pointer spre ntreg; *p va lua din x doar primii 4 octeti si apoi ii va converti la int
// valoarea *p va fi convertita la double si salvata in y
//atentie : nu rezulta eroare de compilare, se da doar un warning
2

6.2. Aritmetica adreselor


Pentru salvarea datelor, un program scris n C poate folosi 3 tipuri de memorie care partajaz o zon comun i anume
zona de date a programului. Astfel, zona de date se imparte in urmtoarele: memoria static, stiva i memoria dinamic
(heap).
Codul aferent funciilor care sunt necesare pentru execuia programului (funcia main precum si toate celelalte funcii care
sunt apelate n program) este salvat ntr-o alt zon de memorie denumit zona de cod.
Zona de date mpreun cu zona de cod reprezint spaiul de memorie alocat de sistemul de operare pentru execuia unui
program.
In cele ce urmeaz, ne vom referi la zona de date. Pointerii obisnuii din program refer adrese din aceast zon.
1. Memoria global sau statica gzduiete variabile definite globale sau variabilele definite cu static (n fiiere, funcii...).
Caracteristica esenial a acestei zone este c ea este alocat i iniializat de compilator nainte ca execuia programului
s intre n funcia main, i exist pn dup ce aceast funcie principal se ncheie. Mrimea zonei globale este determinate
la compilare si este dat de mrimea variabilelor globale i statice din program. Mrimea acestei zone este fix, pe toat
durata execuiei programului. Dac nu se specific altfel, toate variabilele din zona global sunt initializate cu valoarea 0.
2. Stiva este o zon de memorie unde compilatorul aloc spaii conform principiului stivei (LIFO). Aceste spaii sunt
legate strict de funcii, de variabilele definite n cadrul funciilor. n aceast zon se definesc de ctre compilator variabilele
locale (funciilor sau domeniilor de vizibilitate) Aici intr parametrii cu care funcia este apelat i variabilele locale
obinuite - NU i cele precedate de static. Deci, funcia main are zona de stiva A, la intrare n funcie. Dac n main exist
un apel la funcia f(), la apelul acesteia, n stiv se adaug zona B care conine variabilele definite n f. O zon pe stiv
exist ct timp execuia se afl n respectiva funcie sau n funcii apelate mai departe din aceasta. Odat ce funcia
returneaz, zona aferent de stiv este dezalocat, i zona de stiv curent va fi cea aferent a funciei apelante.
3. n memoria dinamic sau heap alocm variabilele dinamic, adic n timpul rulrii programului (cu funcia malloc).
Aceast zon de memorie st la dispoziia programului, care face alocri n funcie de cele petrecute n timpul execuiei.
Pe parcursul execuiei programului, zona de stiv respectiv heap crete si descrete, dup cum programul intr sau iese din
funcii, sau dup cum programatorul aloc / dezaloc variabile dinamice.
Pentru primele 2 tipuri de memorie (zona global i zona de stiv), programatorul tie la momentul scrierii programului
cantitatea de memorie necesar la un anume punct in execuia programului, si anume variabilele disponibile intr-un punct
de execuie, ns pe heap se aloc variabile n funcie de necesarul la rulare. Spre exemplu, un editor text va aloca irul de
caractere pe heap, ct vreme nu tie dac utilizatorul va introduce 10, 100 sau mai multe caractere.
Dac p este un pointer, atunci p++ incrementeaz pe p n aa fel nct acesta s pointeze pe elementul urmtor indiferent
de tipul variabilei pointate, iar p+=i incrementeaz pe p pentru a pointa peste i elemente din locul unde p pointeaz curent.
Dac p i q sunt pointeri, relatii ca <, >, ==, !=, funcioneaz.
p<q
este adevarata, de ex, n cazul n care adresa lui p este mai mica (logic) dect adresa lui q. dac p i q pointeaz ctre 2
elemente ale aceluiai tablou, atunci p < p nseamn faptul c p arat ctre un element cu indice mai mic dect q.
Dac se testeaz cu < > pointeri care sunt adrese efective, rezultatul (din punct de vedere al variabilelor din program) nu
are sens. Ins acest lucru este permis de ctre compilator.
Relatiile == i != sunt i ele permise, intre 2 pointeri. De asemenea, orice pointer poate fi testat cu NULL. Dac un pointer
este == NULL inseamn c adresa de memorie spre care pointeaz este adresa 0, ceea ce e un non-sens.
3

Instructiunea
p + n
desemneaza al n-lea obiect din memorie dupa cel pointat curent de p. Acest lucru este adevarat indiferent de tipul obiectelor
pe care p a fost declarat ca pointer. Compilatorul atunci cind il intilneste pe n, il decaleaza n functie de lungimea obiectelor
pe care pointeaza p, lungime determinata prin declaratia lui p (sizeof(*p)).
Este valid i scderea pointerilor: dac p i q pointeaz pe elementele aceluiai tablou, p-q este numrul de elemente
dintre p i q. Acest fapt poate fi utilizat pentru a scrie o nou versiune a lui strlen.
int strlen(char *s)//returneaz lungimea sirului
{
char *p = s;
while (*p != '\0')
p++;
return(p-s);
}
Prin declarare, p este initializat pe s, adic s pointeze pe primul caracter din s. In cadrul buclei while este examinat fiecare
caracter pn se ntlnete \0 care semnific sfiritul sirului de caractere iar apoi se scad cele 2 adrese.
Este posibila omiterea testului expilcit iar astfel de bucle sint scrise adesea
while (*p)
p++;
Deoarece p pointeaza pe caractere, p++ face ca p sa avanseze de fiecare data pe caracterul urmator, iar p-v da numarul de
caractere parcurse, adica lungimea sirului. Aritmetica pointerilor este consistenta: daca am fi lucrat cu float care ocupa
mai multa memorie decit char, i daca p ar fi un pointer pe float, p++ ar avansa pe urmatorul float.
Toate manipularile de pointeri iau automat n considerare lungimea obiectului pointat n asa fel nct trebuie s nu fie
alterat.
Operaii permise : adunarea sau scaderea unui pointer cu un intreg, scaderea sau compararea a doi pointeri.
Nu este permisa adunarea, impartirea, deplasarea logica, sau adunarea unui float sau double la pointer.

6.3. Transmiterea argumentelor la apelurile de funcii


La apelul unei funcii argumentele se transmit prin stiv, in ordinea n care apar n lista de argumente a funciilor.
Aceasta nseamn c pentru fiecare argument al funciei, pe stiv se creaz o variabil local (funciei) cu numele
argumentului, iar valoarea transmis este utilizat la iniializarea acestei variabile locale. Evident, la sfritul execuiei
funciei, aceste variabile locale fiind salvate pe stiv se pierd, si valorile din ele nu mai sunt disponibile dup terminarea
apelului funciei. Spunem c parametrii s-au transmis funciei prin valoare.
Practic, la transmiterea prin valoare, se creeaz copii temporare ale parametrilor care se transmit. Funcia apelat lucreaz
cu aceste copii iar la revenire valorile variabilelor parametru vor fi nemodificate. Deci este imposibil ca funcia apelat s
modifice unul din argumentele reale din apelant.
Exemplu:
4

#include<stdio.h>
void schimbare(int x, int y)
//se vor crea copii ale variabilelor x i y
{ int tmp;
tmp=x;
x=y;
y=tmp;
//n cadrul copiilor variabilelor se face inversarea
}
//la revenire copiile variabilelor x i y se distrug
int main()
{ int x=5, y=7;
schimbare(x,y);
//transmitere prin valoare
printf(%d %d\n,x,y);//valorile rmn nemodificate adic se va afia 5 7
}

In acest fel, se transmit doar argumentele de tipul input pentru funcii.


Dac dorim ca funcia s modifice valorile variabilelor primite ca i argument iar valorile s se regseasc modificate n
funcia apelant dup finalizarea apelului funciei apelate, trebuie s facem disponibile funciei apelate adresele variabilelor
transmise ca si argumente. In acest caz, spunem c facem transmitere prin adres sau referin.
La transmiterea prin adres, se transmite adresa variabilelor parametru. Toate operaiile n cadrul funciei apelate se fac
asupra zonei originale. Dac se dorete alterarea efectiv a unui argument al funciei apelante, trebuie furnizat adresa
variabilei ce se dorete a se modifica (un pointer la aceast variabil). Funcia apelat trebuie s declare argumentul
corespunztor ca fiind un pointer.
Exemplu:
#include<stdio.h>

void schimbare(int *x, int *y)


{
int tmp;
tmp=*x;
*x=*y;
*y=tmp;
//se face inversarea asupra zonei originale
}
int main()
{ int x=5, y=7;
schimbare(&x,&y);
//transmitere prin adres
printf(%d %d\n,x,y);//valorile sunt inversate adic se va afia 7 5
}

Pentru o funcie, argumentele care sunt de tipul Input-output sau doar output trebuiesc transmise (obligatoriu) prin
adres.
n cazul n care apar masive, avnd n vedere c numele tabloului este un pointer constant spre primul element al lui, se
folosete numele masivului ca adres de nceput. Elementele masivului nu vor fi copiate iar operaiile se vor face astfel
asupra zonei originale.
O utilizare comun a argumentelor de tip pointer (transmitere prin adres) se ntlnete n cadrul funciilor care trebuie s
returneze mai mult dect o singur valoare. De exemplu, constatm faptul c funcia scanf utilizeaz transmiterea prin
adres, datorit faptului c, in argumentele sale, trebuie s regsim valorile care se citesc.
5

6.4. Pointeri i tablouri


In C, exist o relatie strns ntre pointeri i tablouri, nct pointerii i tablourile pot fi tratate simultan. Orice operaie
care poate fi rezolvat prin indici n tablouri, poate fi rezolvat i cu ajutorul pointerilor. Versiunea cu pointeri va fi n
general, mai rapid.
Declaraia
int a[10]
definete un tablou de dimensiunea 10, care este un bloc de 10 variabile de tip int salvate la adrese consecutive numite
a[0], a[1], ..., a[9] notaia a[i] desemneaza elementul deci poziiile in tablou, numrate de la nceputul acestuia.
Daca pa este un pointer pe un ntreg, declarat ca
int *pa
atunci asignarea
pa = &a[0]
face ca pa s pointeze pe al "zero-ulea" (primul) element al tabloului a; aceasta inseamna ca pa conine adresa lui a[0].
Aadar asignarea

x = *pa va copia coninutul lui a[0] n x.

Daca pa pointeaz pe un element oarecare al lui a atunci prin definiie pa+1 pointeaza pe elementul urmator i n general
pa-i pointeaza cu i elemente inaintea elementului pointat de pa iar pa+i pointeaza cu i elemente dupa elementul
pointat de pa.
Astfel, dac pa pointeaz pe a[0]
*(pa + 1)
refera coninutul lui a[1],

pa + i este adresa lui a[i] i *(pa+i) este coninutul lui a[i].

Aceste observaii sunt adevrate indiferent de tipul variabilelor din tabloul a. Definiia "adunrii unitii la un pointer " i
prin extensie, toat aritmetica pointerilor este de fapt calcularea prin lungimea n memorie a variabilei pointat. Astfel, n
pa+i, i este nmulit cu lungimea variabilei pe care pointeaz pa (sizeof(*pa)) nainte de a fi adunate la pa.
Corespondena ntre indexare i aritmetica pointerilor este evident foarte strns. De fapt, numele unui tablou este convertit
de ctre compilator ntr-un pointer constant pe nceputul zonei de memorie unde se afl tabloul.
Efectul este c numele unui tablou este o expresie pointer.
Aceasta are cteva implicaii utile. Din moment ce numele unui tablou este sinonim cu locaia elementului su zero,
asignarea
pa = &a[0]
poate fi scris i pa = a

O referin la a[i] poate fi scris i ca *(a+i).Evalund pe a[i], C l convertete n *(a+i); cele dou forme sunt
echivalente. Aplicnd operatorul & ambilor termeni ai acestei echivalene, rezult ca &a[i] este identic cu a+i: a+i adresa
elementului al i-lea n tabloul a.
Reciproc, dac pa este un pointer el poate fi utilizat n expresii cu un indice; pa[i] este identic cu *(pa+i).
Pe scurt orice tablou i exprimare de indice pot fi scrise ca un pointer i deplasament i orice adres chiar n aceeai
instruciune. Trebuie inut seama de o diferen ce exist ntre numele tablou i un pointer. Un pointer este o variabil,
astfel ca pa=a i pa++ sunt operaii.
Dar, un nume de tablou este o constant, de aceea construcii ca a=pa sau a++ sunt interzise.
Atunci cnd se transmite un nume de tablou unei funcii, ceea ce se transmite este locaia de inceput a tabloului. In cadrul
funciei apelate acest fapt argument este o variabil ca oricare alta astfel ncat un argument nume de tablou este un veritabil
pointer, adica o variabila continind o adresa.
Ne vom putea folosi de aceasta pentru a scrie o nou versiune a lui strlen, care calculeaza lungimea unui sir.
int strlen(char *s) // returneaz lungimea sirului s
{
int n;
char *ps;
for (n = 0, ps = s; *ps != '0'; ps++)
n++;
return n;
}
Ca parametri formali n definirea unei functii
char s[]
i
char *s;
sunt echivalenti; alegerea celui care trebuie scris este determinata n mare parte de expresiile ce vor fi scrise n cadrul
functiei. Atunci cind un nume de tablou este transmis unei functii, aceasta poate, dupa necesitati s-o interpreteze ca
tablou sau ca pointer i sa-l manipuleze n consecinta. Functia poate efectua chiar ambele tipuri de operatii daca i se pare
potrivit i corect.
Este posibil i transmiterea ctre o funcie doar a unei pri dintr-un tablou prin transmiterea unui pointer pe nceputul
subtabloului. De exemplu, dac a este un tablou;
f(&a[2])
si
f(a + 2)
ambele transmit functiei f adresa elementului a[2] deoarece &a[2] i a+2 sint expresii pointer care refera al treilea element
al lui a. n cadrul lui f, declarea argumentului poate citi
f(int arr[])
{
...
}
7

sau
f(int *arr)
{
...
}
Astfel, dupa cum a fost conceput funcia f aptul c argumentul refer de fapt o parte a unui tablou mai mare, nu are
consecine.

6.5 Pointeri pe caractere i functii


Un sir constant scris astfel
" " este un tablou de caractere (de tipul char[]). n reprezentare intern, compilatorul
termin un tablou cu caracterul \0 n aa fel nct programele s poat detecta sfritul.
Lungimea n memorie pentru un sir de caractere ( ) este astfel mai mare cu 1 dect numrul de caractere cuprinse
ntre ghilimele.
char message[40] = "now is the time";
defineste un sir de 40 de caractere i l asigneaz cu sirul de caractere "now is the time".
Exemplu : strcpy(char s[], char t[]) copiaza sirul t n sirul s, versiunea cu tablouri :
int mystrcpy(char s[], char t[]) //copiaza t n s
{
int i;
i = 0;
while ((s[i] = t[i]) != '\0')
i++;
return i;
}

Versiunea lui mystrcpy cu pointeri


strcpy(char *s, char *t) //copiaza t n s, versiunea pointeri
{
while ((*s = *t) != '\0') {
s++;
t++;
}
}

Deoarece argumentele sunt transmise prin valoare (atentie: avem pointeri transmisi prin valoare), mystrcpy poate utiliza s
i t n orice fel se dorete. Aici ei sunt convenional utilizati ca pointeri, care parcurg tablourile pina n momentul n care
s-a copiat \0 sfirsitul lui t, n s.
In practic, mystrcpy nu va fi scris asa cum s-a aratat mai sus. O a doua posilitate ar fi
int mystrcpy(char s[], char t[]) //copiaza t n s
{
int i = 0;
while ((*s++ = *t++) != '\0') i++;
return i;
}

In aceasta ultim versiune se imit incrementarea lui s i t n partea de test. Valoarea lui *t++ este caracterul pe care a
pointat inainte ca t sa fi fost incrementat; prefixul ++ nu-l schimba pe t inainte ca acest caracter sa fi fost adus.nacelasi
fel, caracterul este stocat n vede a pozitie s inainte ca s sa fie incrementat. Acest caracter este deasemenea valoarea care
se grupeaza cu \0 pentru simboul buclei. Efectul net este ca, caracterele sint copiate din t n s, inclusiv sfirsitul lui \0.
Ca o ultima abreviere vom observa ca i gruparea cu \0 este redundant, astfel c bucla while poate fi scris:
while (*s++ = *t++)
Desi aceasta versiune poate prea complicata la prima vedere, aranjamentul ca notatie este considerat suveran daca nu
exista alte raiuni de a schimba.
Rutina este mystrcmp(s, t),compara sirurile de caractere s i t i returneaza negativ, zero sau pozitiv n functie de
relatia dintre s i t; care poate fi: s<t, s=t sau s>t.
Valoarea returnat este obinut prin scderea caracterului de pe prima poziie unde s difer de t.
int mystrcmp(char s[], char t[])
{
int i;
i = 0;
while (s[i] == t[i])
if (s[i++] == '\0')
return(0);
return(s[i] - t[i]);
}

// returneaza <0 daca s<t, 0 daca s==t, >0 daca s>t

Versiunea cu pointeri a lui strcmp.


int mystrcmp(char s[], char t[]) // returneaza <0 daca s<t, 0 daca s==t, >0 daca s>t
{
for ( ; *s == *t; s++, t++)
if (!*s)
return(0);
return(*s - *t);
}

Dac ++ i -- sunt folosii altfel dect operatori prefix sau postfix pot apare alte combinaii de * i ++ i --, dei mai puin
frecvente.
Exemplu: *++p incrementeaza pe p inainte de a aduce caracterul pe care pointeaza p.
*--p decrementeaz pe p n acelasi condiii.
La lucrul cu siruri de caractere, se prefer iterarea folosind pointeri de tipul char*, si nu indeci pe sir.

6.6 Pointeri pe caractere i functii


Se solicit atenie sporit la initializarea sirurilor de caractere.
Astfel, dac n program scriem un sir de caractere utilizand semnul , programul va aloca sirul ca i o constant i il va
pstra la o zon de memorie care este dincolo de controlul nostru in program.
Astfel, irul how are you? este tratat de ctre program ca i o constant de tipul char[], iar pentru aceast constant se
aloc 13 octeti (12 octei pentru caracterele sirului i un octet pentru \0).
9

Urmtoarea initializare este valid i corect:


char sir[30] = how are you?;
astfel, compilatorul aloc o zon de memorie pentru variabila sir , de mrime 30 octei, iar pe aceast zon copiaz sirul
how are you?.
Daca scriem:
char *psir = how are you?;
atunci compilatorul aloc o constant de tipul char* cu valoarea how are you? i face ca pointerul psir s arate ctre zona
de memorie unde este alocat constanta de tipul char*. Aceasta inseamn c daca vom dori s modificm continutul zonei
de memorie pointate de psir (care este constant) va genera o eroare de compilare.
Concluzia este c dac dorim s utilizm variabile (neconstante) de tip sir de caractere in program, avem 2 alternative:
1. Definim variabila ca si sir de caractere folosind sintaxa de sir : char* sir[dimensiune]. In acest caz, se aloc (in
zona global sau zona de stiv) variabila sir de dimeniunea specificiat, iar mai apoi vom putea manipula aceast
zon de memorie dup cum dorim
2. Definim variabila ca i pointer de tipul char*. : char *psir;. Apoi va trebui s alocm dinamic acest sir folosind
funcia de alocare malloc. Dup aceea vom putea manipula continutul acestei zone de memorie. La finalul utilizrii,
programatorul trebuie s s dezaloce zona de memorie folosind funcia free. Alocarea de memorie este realizat
dinamic, in zona de heap.
In ambele cazuri, programatorul va manipula continutul zonei de memorie folosind funciile de lucru pe siruri de caractere
din string.h: strcpy, strcmp, strcat etc.
In cazul 2, programatorul trebuie sa aloce o zon de memorie cel putin egal cu lungimea sirului care se doreste a fi salvat
+ 1, si anume strlen(sir) + 1
Pe parcursul programelor pe care le scrie, vom evita s realizm atribuiri de tipul:
pchar = text;
pentru ca acestea nu au sens: ele fac variabila pointer de tipul char* s pointeze ctre o zon de memorie dincolo de
controlul programului nostru.

10

S-ar putea să vă placă și