Академический Документы
Профессиональный Документы
Культура Документы
Pointeri
Pointerii sunt variabile sau constante care au ca valori adrese ale unor variabile,
adică adrese ale unor locaţii de memorie. Ei permit calcule cu adrese, calcule care sunt
specifice limbajelor de asamblare şi sunt folosiţi în scopul scrierii unor programe mai
eficiente atât din punctul de vedere al timpului de execuţie cât şi din punctul de vedere al
utilizării resurselor hard, în mod concret al utilizării memoriei computerelor.
Pointerii sunt în mod special utili la alocarea dinamică a memoriei [Beu99] şi la apelul prin
referinţă.
Pentru a înţelege mai uşor pointerii este convenabil ca aceştia să fie discutaţi prin
comparaţie cu variabilele normale.
O variabilă "normală" este o locaţie de memorie care păstrează o anumită valoare. Am
văzut în capitolele precedente că la declararea unei variabile, acesteia i se alocă un anumit
număr de octeţi, iar referirea la valoarea stocată în acei octeţi se face prin numele
respectivei variabile. La nivel maşină, respectiva locaţie are o adresă de memorie. Dacă de
exemplu, variabila declarată este s de tip float, cei 4 octeţi care încep la adresa locaţiei
respective sunt cunoscuţi programatorului ca şi s şi pe aceşti 4 octeţi pot fi stocate valori
reale în simplă precizie.
Pointerii nu păstrează o valoare în sens tradiţional ci adresa unei variabile. Un
pointer indică ("pointează") o altă variabilă păstrând o copie a adresei acestei variabile.
Deoarece pointerii păstrează adrese şi nu valori "tradiţionale", ei pot fi văzuţi ca fiind
1
Curs 7 Pointeri
dereferenţiere. Pentru iniţializarea pointerilor se poate folosi operatorul unar &, numit
operator de referenţiere şi care returnează adresa argumentului său.
Declararea pointerilor se face în modul următor:
tip *nume_pointer; sau tip* nume_pointer;
unde tip este un tip de date de bază sau tip utilizator, iar nume_pointer este un
identificator folosit pentru pointerul care se declară.
Exemple
1. float *pf; //pf poate conţine adrese la care se află memorate date de tip float
int *pi; //pi poate conţine adrese la care se află memorate date de tip int
char* pc; //pc poate conţine adrese la care se află memorate date de tip char
2. struct agenda
{
char nume[20];
char tel[15];
};
Iniţializarea pointerilor constă în precizarea adresei unui obiect definit în memorie. Dacă p
este numele unui pointer, atunci *p reprezintă valoarea de la adresa la care "pointează"
pointerul p.
Exemple
1. double a, *pd;
a=8.406;
pd=&a;
Program exemplu
#include <stdio.h>
#include <math.h>
void main()
{
float a,b;
float* p;
p = &a;
*p=4*atan(1.0);
b=2*a;
printf("a=%f b=%f",a,b);
printf("\nValoarea din locatia de memorie la care pointeaza p este %f
\nAdresa lui a: %X", *p, p);
}
Exemplu
int* pi=0;
...
if(pi!=0)
n=*pi; //se foloseşte pointerul dacă pointează la o adresă legală
4
Curs 7 Pointeri
În loc de valoarea 0 (zero) se poate folosi macroul NULL, această valoare pentru un pointer
indicând de asemenea o adresă nevalidă. Astfel, exemplul precedent poate fi rescris sub
forma:
#include <stdio.h>
#include <math.h>
void main()
{
int* pi=NULL;
int n;
scanf("%d",&n);
if(n<10)
pi=&n;
if(pi)
{
printf("dublul lui n este %d",2*(*pi));
printf("\nAdresa lui n este %X",pi);
}
else
printf("Adresa nevalida: \\x%X",pi);
}
respectiv:
In exemplele discutate până acum, am văzut că pointerii por fi iniţializaţi fie prin
asignarea valorilor adreselor unor variabile sub forma pi=&i, fie prin asignarea valorii
NULL. Pointerii mai pot fi iniţializaţi cu ajutorul funcţiilor care returnează un pointer. Vom
discuta aceste cazuri în capitolul dedicat managementului fişierelor.
Operaţii cu pointeri
Cu pointeri pot fi efectuate următoarele operaţii:
a) Incrementare şi decrementare
Fie declaraţia:
int *p;
Instrucţiunile:
++p; şi p++;
respectiv:
--p; şi p--;
măresc, respectiv micşorează valoarea lui p cu o unitate. În acest caz, unitatea este de 2
octeţi deoarece aceasta este lungimea locaţiilor de memorie în care se păstrează datele de
tip int.
5
Curs 7 Pointeri
Fie declaraţiile:
int n,*pin=&n;
float x,*pre=&x;
atunci, dacă pin are valoarea 1BBC, expresia pin+3 va avea valoarea 1BBC+6, adică 1BC2.
Dacă pre are valoarea 1BB6, atunci expresia pre-5 va avea valoarea 1BB6-20, adică 1BA2.
c) Scăderea pointerilor
În limbajul C nu este permisă adunarea pointerilor dar este permisă scăderea
acestora. Prin scăderea a doi pointeri se obţine un întreg care reprezintă numărul locaţiilor
de memorie aflate între adresele la care pointează cei doi pointeri care se scad.
Astfel, o instrucţiune de forma:
pmed=(pin+pfin)/2;
va semnala o eroare de forma "Invalid pointer addition", în timp ce instrucţiunea:
pmed=pin+(pfin-pin)/2;
este corectă deoarece în acest caz, la pin se adună întregul rezultat din diferenţa pfin-pin
împărţită la 2.
d) Compararea pointerilor
Comparaţiile logice !=, ==, <, <=, >, >= sunt valabile şi în cazul pointerilor.
Mai jos este dat ca exemplu o funcţie care determină lungimea unui şir de caractere.
Menţionăm că numele unui tablou, deci şi al unui şir, este un pointer constant care are ca
valoare adresa primului element al tabloului.
int lsir(char *pc)
{
int i;
for(i=0;*pc!=NULL;pc++)
i++;
return i;
}
Când funcţia va fi apelată cu numele unui şir, pointerul pc va avea ca valoare adresa
primului caracter al şirului. În funcţia lsir se incrementează i cât timp caracterul la care
pointează pc este nenul. Orice şir se încheie cu caracterul NULL.
Pointeri şi tablouri
Aşa cum am mai spus, în limbajul C, pointerii sunt intim legaţi de tablouri, numele
unui tablou fiind chiar un pointer constant care are ca valoare adresa elementului de index
0 al tabloului respectiv. Orice operaţie care se face folosind indicii tablourilor poate fi
făcută, chiar mai rapid, prin folosirea pointerilor.
Exemple
1. char text[10]="Anul1Fizica", *pc;
6
Curs 7 Pointeri
pc=text;
putcahr(*pc); //se va tipări caracterul A
pc+=4;
putchar(*pc); //se va tipări caracterul 1
2. char sir[10]="BorlandC";
char *p;
p=sir;
while(*p)
putchar(*p++); //se va tipări sirul BorlandC
3. double a[15],t;
t=*(a+3); //este echivalent cu t=a[3];
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
void main()
{
Programul de mai sus generează 100 de valori aleatoare în intervalul [-500,500] după care
calculează suma şi media acestor valori.
Exemplu
Iniţializarea elementelor unui tablou fără pointeri şi cu pointeri
În primul caz, pentru aflarea adresei elementului x[i] compilatorul generează un cod care
înmulţeşte pe i cu dimensiunea unui element al tabloului. În varianta cu pointeri,
compilatorul evaluează o singură dată indicele şirului, salvând 49 de operaţii de
multiplicare.
Referirea la elementul unui tablou sub forma a[i] este identică cu *(a+i). De fapt,
imediat ce se întâlneşte o referinţă de forma a[i], compilatorul C converteşte aceasta la
*(a+i). Evident, construcţiile &a[i] şi a+i sunt de asemenea identice.
În acest caz, funcţiei i se transferă un pointer şi anume tocmai numele tabloului care
trebuie tipărit.
8
Curs 7 Pointeri
Program exemplu: ordonarea unui şir folosind pointeri şi funcţie care returnează un
pointer la şirul ordonat
#include <stdio.h>
return p0;
}
void main()
{
float x[100];
int n,i;
scanf("%d",&n);
for(i=0;i<n;i++)
{
printf("x[%d]= ",i);
scanf("%f",&x[i]);
}
ordsir(x,n);
9
Curs 7 Pointeri
printsir(x,n);
}
Un alt exemplu în care este necesar ca apelul funcţiei trebuie să se facă prin valoare este
acela al unei funcţii care să realizeze interschimbarea a două valori.
void main()
{
double* pd;
double a,b;
printf("Adresa la care incepe codul functiei sc1 este: %X\n",sc1);
scanf("%lf%lf",&a,&b);
sc1(a,b);
printf("\nApelul functiei sc1:\na= %lg\tb=%lg",a,b);
sc2(&a,&b);
printf("\nApelul functiei sc2:\na= %lg\tb=%lg",a,b);
}
După cum se vede, funcţia sc1 nu interschimbă valorile variabilelor din funcţia apelantă ci
numai copii ale acestora. Pe de altă parte, funcţia sc2, al cărui apel se face prin referinţă,
realizează interschimbarea celor două valori. Aşadar, argumentele de tip pointer permit
funcţiilor să acceseze şi să modifice valorile variabilelor funcţiei apelante.
Exerciţiu
Să se scrie o funcţie care să returneze 1 dacă se citeşte un număr de la tastatură şi să
returneze 0 în caz contrar. Vezi funcţia getint din [Ker88].
10
Curs 7 Pointeri
Pointeri la funcţii
La fel ca şi numele unui tablou şi numele unei funcţii este un pointer la funcţia
respectivă, adică numele funcţiei este o reprezentare în mod intern a adresei la care începe
codul funcţiei. Pointerii la funcţii se utilizează pentru transmiterea funcţiilor ca parametri
ai altor funcţii.
etc....
Exemplu
Dacă dorim ca funcţia f să apeleze funcţia g sub forma f(g), funcţia g având antetul:
float g(int x)
atunci antetul lui f trebuie să fie de forma:
double f (float (*) (int))
Exemplu
//Funcţia bisect are ca parametru o altă funcţie f
Exemple
char *pr[1000]; //tablou cu maxim 1000 de elemente; elementele sunt pointeri la tipul char
int *ptr[100]; //tablou cu maxim 100 de elemente; elementele sunt pointeri la tipul int
Fig.1
respective. La fiecare apel al unei funcţii, variabilele sale locale şi parametrii săi sunt
împinse în memoria stivă. La ieşirea din funcţie, aceste variabile şi parametri sunt eliminaţi
din stivă. Din această cauză, mărimea memoriei stivă a unui program, chiar dacă are o
valoare maximă, se modifică continuu în timpul execuţiei programului.
Memoria heap este o zonă de memorie complet separată, controlată de către un
manager de memorie de tip "run-time", care face managementul memoriei în timpul
execuţiei programului. De fapt acest manager de memorie este o funcţie de bibliotecă
adăugată programului aflat în execuţie.
Dacă un program foloseşte structuri dinamice în memoria heap, atunci când aceste
structuri nu mai sunt folosite, managerul de memorie va marca blocurile de memorie
corespunzătoare acestora ca fiind libere. Atunci când un program de aplicaţii îşi încheie
execuţia, sistemul de operare va descărca acel program din memorie, precum şi variabilele
sale globale şi stiva sa, astfel încât un alt program va putea folosi spaţiul corespunzător de
memorie. În acest fel, memoria computerelor este în permanenţă reciclată şi refolosită de
către programe pe măsură ce acestea se află în execuţie şi se încheie.
Memoria heap este disponibilă programelor de aplicaţii în timpul execuţiei acestora
folosind funcţiile malloc şi free. Acestea permit ca unui program să i se aloce memorie
exact atunci când este nevoie în timpul execuţiei programului.
13
Curs 7 Pointeri
void main()
{
int n,i;
float *p,*p0;
14