Sunteți pe pagina 1din 11

Pointeri

Introducere

Pointerii sunt variabile care contin adresa de memorie a unei alte variabile. Din aceste considerente,
pointerii se numesc si variabile de adresa.
Presupunem ca avem o variabila de tip întreg numita entitate localizata la adresa de memorie 0x1000
(adresele sunt automat asigante variabilelor de catre compilator). Daca dorim sa referim aceasta
variabila prin intermediul unui pointer entitate_ptr, atunci valoarea pointerului va fi 0x1000
(entitate_ptr = 0x1000), astfel spunem ca entitate_ptr "arata" spre variabila entitate (Pentru a se evita
confuziile, se recomanda ca numele pointerilor sa aiba sufixul _ptr).
Pentru a întelege mai bine mecanismul de functionare a pointerilor, introducem o analogie pointeri -
adrese postale, în care adresa de memorie este adresa postala, numele variabilei pointer este numele
cladirii, iar entitatea referita este cladirea propriu-zisa. Aceasta analogie este prezentata în tabelul 7.1.
Se observa ca doi pointeri diferiti (Fac_AC si Fac_ETC) au aceeasi valoare, indicând spre aceeasi
locatie, astfel referind aceeasi entitate.

Tabela 7.1: Analogie pointeri - adrese postale


Variabila (nume pointer) Valoare adresa Entitate
Fac_AC Bd. V. Pârvan 2 Cladire Electro
Fac_ETC Bd. V. Pârvan 2 Cladire Electro
Fac_Mate Bd. V. Pârvan 4 Cladire "U"

Operatii cu pointeri

Pointerii se declara punând un asterisc (*) în fata numelui variabilei:

int variabila; /*definire variabila intreg*/


int *variabila_ptr; /*definire pointer la un intreg */

Operatorii specifici pointerilor sunt: operatorul de dereferentiere * (pentru un pointer, returneaza entitatea
referita de pointer) si operatorul de adresa & (pentru o entitate, returneaza adresa entitatii). Exemplul urmator
explica folosirea acestor operatori. Variabila pointer obiect_ptr primeste adresa variabilei obiect, astfel
obiect_ptr va indica spre zona de memorie unde este memorata variabila obiect. În ultima linie, se stocheaza
valoarea 5 la zona de memorie spre care arata obiect_ptr, adica în variabila obiect.

int obiect; /* variabila de tip intreg */


int * obiect_ptr; /* pointer la un intreg */

obiect=4;
obiect_ptr=&obiect; /* obiect_ptr va indica
spre obiect */
printf("%d\n", *obiect_ptr); /* afiseaza valoarea
spre care indica
obiect_ptr */
*obiect_ptr=5; /* atribuie lui obiect
valoarea 5 */

Folosirea gresita a operatorilor poate duce la functionari incorecte a programului sau, în cazul fericit, la erori
semnalate de compilator:

*variabila
1
este ilegal, prin aceasta se solicita entitatea referita de o variabila care nu este un pointer;
&variabila_ptr
este legal, dar "neobisnuit", se solicita adresa lui variabila_ptr, adica un pointer la un pointer.

Mai multi pointeri pot indica spre aceeasi zona de memorie:

int obiect;
int *obiect_ptr1;
int *obiect_ptr2;

obiect = 1; /* se atribuie valoarea 1 lui obiect */

obiect_ptr1 = &obiect;
obiect_ptr2 = obiect_ptr1;
/* obiect_ptr1 si obiect_ptr2 vor indica spre
aceasi locatie de memorie */
printf("%d %d\n", *obiect_ptr1, *obiect_ptr2);

Un pointer special definit în limbajul C este pointerul NULL, care nu indica spre nimic (adresa spre care arata
acest pointer este 0). Acest pointer este definit în stdio.h si în stdlib.h ca fiind (void *) 0, adica un pointer spre o
locatie de tip void si care arata spre locatia de memorie 0.

O situatie în care pointerii sunt utili este declararea structurilor recursive. În C nu se permite ca o structura sa
contina câmpuri de tipul aceleiasi structuri. Aceasta restrictie poate fi evitata folosind pointeri la structuri, dupa
cum se arata în exemplul urmator:

typedef struct pers


{
char nume[20];
int varsta;
struct pers * urmator;
} persoana;

persoana *p;

Pentru a referi un câmp folosind un pointer spre o structura, se foloseste operatorul de dereferentiere: (*p).nume.
Echivalent cu acest operator exista în C un alt operator pentru accesarea câmpurilor din structurile indicate de

pointeri: p- nume.

Pointeri ca argumente ale functiilor

În C, transmiterea parametrilor la functii se face "prin valoare", singurul rezultat vizibil în exterior
fiind valoarea pe care o returneaza functia. Pentru ca o functie sa poata returna mai multe valori sau
pentru ca modificarile asupra parametrilor sa se propage în exteriorul functiei, se folosesc pointeri.
Acest concept este ilustrat de urmatoarea analogie: Presupunem ca se întâlnesc doua persoane A si B,
A poate doar sa vorbeasca, iar B poate doar sa asculte. Cum poate B sa îi transmita informatii lui A ?
Simplu: A îi spune: "Lasa raspunsul tau în cutia postala numarul x". Astfel, în momentul când se
transmite un pointer catre o functie, functiei i se "comunica" adresa variabilei transmise ca parametru,
putând astfel face modificari asupra acelei variabile.

În exemplul urmator se arata cum functia inc_contor va modifica valoarea variabilei contor.

#include <stdio.h>

2
void inc_contor(int *contor_ptr)
{
(*contor_ptr)++;
}

void main(void)
{
int contor = 0;

while (contor<10)
inc_contor(&contor);
printf("contor: %d\n",contor);
}

Din functia main se transmite spre functia inc_contor nu variabila care trebuie incrementata, ci adresa
sa. Functia inc_contor primeste ca parametru nu un întreg, ci un pointer la un întreg. Astfel, cunoscând
adresa variabilei contor, functia poate modifica valoarea acelei variabile, referind-o indirect prin
intermediul pointerului.

Pointeri si tablouri

În C, exista o strânsa legatura între pointeri si tablouri. Când se foloseste numele unui tablou (fara
index), se genereaza un pointer catre primul element al tabloului. Astfel, în C se pot transmite tablouri
ca parametri în functii (de fapt, se transmite adresa de început a tabloului). Alt avantaj al tratarii
unitare pointer-tablou este aplicarea aritmeticii pointerilor pentru accesarea elementelor unui tablou.
Identitate pointer-tablou se reflecta cel mai bine în operatiile cu siruri de caractere, unde sirul de
caractere este un tablou de caractere sau un pointer la caracter.

Prezentam în cele ce urmeaza unele aspecte ale relatiei dintre pointeri si tablouri.

int tablou[5];
int *tablou_ptr;

tablou_ptr=&tablou[0];
Dupa secventa de mai sus, pointerul tablou_ptr va indica spre primul element al tabloului, deci spre
începutul zonei de memorie unde este stocat tabloul. Deoarece variabila tablou este vazuta în C ca un
pointer, acelasi efect s-ar fi obtinut prin atribuirea: tablou_ptr=tablou;.

Pentru a accesa elementele tabloului, se incrementeaza valoarea pointerului, astfel daca


tablou_ptr=tablou; atunci *(tablou_ptr++) va returna valoarea lui tablou[0], dupa care tablou_ptr va
arata spre tablou[1].

Atentie! *(tablou_ptr++) va returna valoarea lui tablou[0], dupa care se va incrementa valoarea lui
tablou_ptr, aratând spre tablou[1]. În schimb, (*tablou_ptr)++ va incrementa valoarea spre care arata
tablou_ptr, returnând astfel tablou[0]++.

În acelasi context, propunem ca exercitiu rularea urmatorului exemplu:

#include <stdio.h>

void main(void)
{
int t[4]={0, 1, 2, 3};
3
int *p=&t[1];

printf("%d\n", *p++); /* afiseaza valoarea


lui t[1] */
printf("%d\n", *++p); /* afiseaza valoarea
lui t[3] */
printf("%d\n", ++*p); /* afiseaza valoarea
incrementata a lui t[3] */
}

Pointeri la functii

Limbajul C permite nu doar referirea unor date, ci si referirea functiilor (pointeri la functii). Aceasta
nu difera mult de referirea datelor, un pointer la o functie retinând adresa de memorie unde începe
rutina care implementeaza functia.

Un pointer la o functie se declara în maniera:

tip_returnat (*nume_pointer)(... declaratii parametri ...);

Astfel, daca avem implementata o functie f1 si un pointer spre functie f1_ptr, secventa urmatoare va
arata cum se poate realiza prin intermediul unui pointer apelul unei functii:

#include <stdio.h>

void f1(int a)
{
printf("%d\n",a);
}

void main(void)
{
void (*f1_ptr)(int);

f1_ptr=f1; /* pointerul f1_ptr va indica


spre functia f1 */
(*f1_ptr)(4); /* prin intermediul lui f1_ptr
se apeleaza functia f1 */
}
La fel ca în cazul tablourilor, putem atribui direct unui pointer adresa functiei, fara a folosi operatorul
& (f1_ptr=f1; este identic cu f1_ptr=&f1;).

Folosirea neadecvata a operatiilor cu pointeri

În C, deoarece limbajul ofera o mare libertate de exprimare, este foarte usor pentru un programator
neatent sa faca erori care nu sunt semnalate la compilare, dar care duc la comportari neasteptate ale
programului.

O eroare tipica este atribuirea de valori pointerilor neinitializati:

4
int *un_pointer;
*un_pointer=5;
În aceasta situatie, deoarece un_pointer este neinitializat, valoarea 5 este scrisa în memorie la o locatie
aleatoare, poate chiar rezervata altei variabile. În cazul în care un pointer nu arata spre o zona de
memorie rezervata, trebuie alocata o zona de memorie în mod explicit, folosind functia malloc:
un_pointer=(int *)malloc(sizeof(int));
Functia malloc rezerva spatiu de memorie si returneaza adresa spatiului rezervat; zona de memorie
referita de un pointer poate fi eliberata folosind functia free. Ambele functii sunt definite în stdlib.h.

În C nu exista un mecanism de garbage collection, astfel ca programatorul trebuie sa aiba grija ca


zonele de memorie alocate dinamic si care nu mai sunt utile sa fie dealocate (folosind functia free).
Faptul ca unui pointer i se atribuie o noua valoare nu înseamna ca zona de memorie spre care arata s-a
eliberat si în acest mod programul poate ajunge sa tina alocata toata memoria disponibila.

O alta greseala des întâlnita este referirea pointerilor spre alte locatii de memorie:

int obiect=5;
int *obiect_ptr;

obiect_ptr = obiect;
Lipsa operatorului de adresa & face ca obiect_ptr sa indice spre locatia de memorie aflata la adresa 5.

Urmatorul exemplu ilustreaza un stil de programare "daunator", care strica lizibilitatea codului.
Limbajul C este foarte flexibil, fapt ce poate duce, în special în cazul pointerilor, la rezultate
imprevizibile:

void copy_string(char * p, char *q)


{
/* copiaza sirul q in p */
while (*p++ = *q++);
}
Chiar daca aceasta implementare este mult mai compacta, urmarirea codului este foarte dificila, iar
folosirea combinata a operatorilor de incrementare si pointerilor poate duce la efecte necontrolate.
Urmatoarea varianta de implementare a aceleiasi functii copy_string prezinta un stil adecvat
programarii cu pointeri:
void copy_string(char * dest, char * sursa)
{
*dest = *sursa;
while (*dest!='\0')
{
++dest;
++sursa;
*dest = *sursa;
}
}

Problema rezolvata

Sa se scrie un program care gestioneaza date despre un grup de studenti. Pentru fiecare student se
memoreaza numele si numarul sau matricol. Programul trebuie sa implementeze urmatoarele operatii:

• citirea numarului de studenti si a datelor acestora;


5
• afisarea datelor tuturor studentilor;
• sortarea listei de studenti în ordinea alfabetica a numelor;
• sortarea listei de studenti în ordinea crescatoare a numerelor matricole;
• cautarea unui student pentru care se precizeaza numele si afisarea pozitiei pe care o ocupa
acesta în lista ordonata alfabetic dupa numele studentilor;
• cautarea unui student pentru care se precizeaza numarul matricol si afisarea pozitiei pe care o
ocupa acesta în lista ordonata crescator dupa numarul matricol al studentilor.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <conio.h>

typedef struct
{
char *nume;
int nr;
} student;

#define SIZE sizeof(student)

typedef int (*comp)(void *, void *); /* tip pointer la


functia de comparare */
typedef student *pstud;

/*--------------------------------------------------------*/
/* */
/* functia eroare afiseaza un mesaj de eroare,in */
/* cazul in care nu poate fi alocata dinamic o zona */
/* de memorie; apoi se opreste executia programului */
/* */
/*--------------------------------------------------------*/

void eroare(void)
{
puts(" \n **** eroare alocare dinamica de memorie ****");
exit(1);
}

/*--------------------------------------------------------*/
/* */
/* citeste numarul de studenti si datele acestora, */
/* aloca dinamic spatiu pentru tabloul de studenti, */
/* memoreaza numele si nr. matricol ale studentilor */
/* */
/*--------------------------------------------------------*/

void citeste(int *n, pstud *tab)


{
pstud t;
char sir[40];
int i;

6
/* citeste numarul de studenti */
printf("\n dati numarul de studenti:");
scanf("%d", n);

/* aloca spatiu pentru toate inregistrarile de tip student */


if (!(t=(pstud)malloc((*n)*SIZE))) /* citeste1 */
eroare();
*tab=t;

/* citeste datele tuturor studentilor */


for (i=0; i<*n; i++, t++)
{
printf("\n nume: ");
scanf("%s", sir);
/* aloca dinamic spatiu pentru numele studentului */
if (!(t->nume=(char *)malloc(strlen(sir)+1))) /* citeste2 */
eroare();
strcpy(t->nume, sir);
printf("\n numar matricol:");
scanf("%d", &t->nr);
}
}
/*--------------------------------------------------------*/
/* */
/* afiseaza numele si numarul matricol ale celor */
/* n studenti din tabelul a carui adresa este data */
/* in parametrul tab */
/* */
/*--------------------------------------------------------*/

void afiseaza (int n, pstud tab)


{
int i;

puts("\n tabelul cu studenti ");


for (i=0; i<n; i++, tab++)
printf("\n%-30s %4d", tab->nume, tab->nr);
}

/*--------------------------------------------------------*/
/* */
/* functia comp1 compara numele a doi studenti ale caror */
/* adrese sunt date de pointerii p si r */
/* */
/*--------------------------------------------------------*/

int comp1(void *p, void *r)


{
return strcmp(((pstud)p)->nume, ((pstud)r)->nume);
}

/*--------------------------------------------------------*/
/* */
/* functia comp2 compara nr. matricol a doi studenti ale */
7
/* caror adrese sunt date de pointerii p si r */
/* */
/*--------------------------------------------------------*/

int comp2(void *p, void *r)


{
return ((pstud)p)->nr - ((pstud)r)->nr;
}

/*--------------------------------------------------------*/
/* */
/* functia cauta studentul cu adresa data de parametrul s */
/* in tabelul cu adresa indicata de parametrul tab; */
/* tabelul este ordonat dupa criteriul specificat prin */
/* parametrul f */
/* */
/*--------------------------------------------------------*/

void cauta(pstud s, pstud tab, int n, comp f)


{
pstud t=NULL;
int i;

t=bsearch(s, tab, n, SIZE, f);


if (t)
{ /* exista studentul cautat */
i=t-tab;
printf("\n studentul %s cu numarul matricol %d e al %d -lea
in evidenta", t->nume, t->nr, i+1);
}
else
printf("\n studentul nu se afla in evidenta");
}

/*--------------------------------------------------------*/
/* */
/* elibereaza spatiul de memorie detinut de campurile nume*/
/* ale celor n studenti din tabel si apoi zona detinuta */
/* de tabel */
/* */
/*--------------------------------------------------------*/

void elibereaza(pstud tabel, int n)


{
int i;

for (i=0; i<n; i++)


free(tabel[i].nume);
free(tabel);
}

/*--------------------------------------------------------*/
8
/* */
/* functia meniu afiseaza meniul programului */
/* */
/*--------------------------------------------------------*/

void meniu(void)
{
puts("\n c, C --- citeste tabel studenti");
puts(" a, A --- afiseaza tabel studenti");
puts(" n, N --- ordoneaza dupa nume");
puts(" r, R --- ordoneaza dupa numar matricol");
puts(" f, F --- cauta dupa nume");
puts(" l, L --- cauta dupa numar matricol");
puts(" x, X --- iesire din program");
}

void main(void)
{
char opt;
int n; /* numarul de studenti */
char nume[30];
student s;
pstud tabel=NULL; /* adresa tabloului cu studenti */

while (1)
{
meniu();
opt=tolower(getche());
switch (opt)
{
case 'c':
if(tabel) /* daca a existat anterior un
alt tablou in memorie */
elibereaza(tabel,n);
citeste(&n, &tabel);
break;
case 'a':
afiseaza(n,tabel);
break;
case 'n':
qsort(tabel, n, SIZE, comp1);
break;
case 'r':
qsort(tabel, n, SIZE, comp2);
break;
case 'f':
printf("\n dati numele:");
scanf("%s", nume);
if (!(s.nume=(char *)malloc(strlen(nume)+1)))
eroare();
strcpy(s.nume,nume);
cauta(&s, tabel, n, comp1);
free(s.nume); /* elibereaza spatiul alocat pentru nume */
9
break;
case 'l':
printf("\n dati numarul matricol:");
scanf("%d", &s.nr);
cauta(&s, tabel, n, comp2);
break;
case 'x':
exit(0);
default:
puts("comanda gresita");
}
}
}

Observatii:

Din considerente de economie de memorie, implementarea tabloului cu studenti s-a facut dinamic;
dupa ce s-a citit numarul de studenti, s-a alocat spatiul corespunzator (vezi linia /* citeste1 */). În plus,
în tablou, în câmpul nume se pastreaza nu sirul de caractere pentru numele studentilor, ci un pointer la
sirul de caractere. Astfel, în loc de a rezerva, pentru toate numele, acelasi numar de caractere, în
momentul în care se cunoaste exact numele unui student, se aloca dinamic un spatiu de memorie de
dimensiune egala cu lungimea acestuia (vezi linia /* citeste2 */). Adresa acestui spatiu se pastreza în
câmpul nume.

Reamintim ca în limbajul C, mecanismul de transmitere a parametrilor este prin valoare. Prin urmare,
o functie nu poate modifica valoarea unui parametru actual, chiar daca modifica parametrul formal
corespondent. Pentru situatiile în care se doreste sa se transmita informatie de la functia apelata la cea
apelanta prin intermediul parametrilor se procedeaza in felul urmator: la apelul functiei se transmite
adresa obiectului care urmeaza sa se modifice, iar, în cadrul functiei, parametrul corespondent se
declara de tipul pointer. Astfel, functia apelata cunoaste adresa obiectului respectiv si îl poate
modifica, operând indirect asupra lui, prin intermediul pointerului. Din aceste considerente, deoarece
citirea numarului de studenti si alocarea spatiului pentru tablou se face în functia citeste, parametrii
formali corespunzatori int *n si pstud *tab vor fi de tipul pointer. La apelul functiei citeste se vor
transmite nu valorile lui n si tabel, ci adresele acestora: &n, &tabel.

La accesarea elementelor tabloului de studenti s-a folosit atât lucrul cu pointeri în functiile citeste si
afiseaza, cât si varianta cu indici în functia elibereaza.

Pentru compararea, dupa nume, a doi studenti s-a implementat functia comp1. Similar, functia comp2
realizeaza compararea dupa numarul matricol.

Pentru sortarea tabloului se apeleaza functia de biblioteca qsort. Aceasta are urmatorul prototip:

void qsort(void *tab, int nr_elem, int nr_oct_elem, int (*fcmp)(void *, void *));

Parametrii indica adresa tabloului de sortat, numarul elementelor tabloului, marimea în octeti a fiecarui
element si adresa functiei de comparare.

Dupa sortarea tabloului, cautarea unui student se face cu functia de cautare binara bsearch:

void *bsearch(void *cheie, void *tab, int nr_elem, int nr_octeti_elem, int (*fcmp)(void *,void *));

10
Parametrii indica cheia cautata, adresa tabloului, numarul elementelor tabloului, marimea în octeti a
fiecarui element si adresa functiei de comparare. Functia returneaza adresa primului element care
corespunde cheii, respectiv valoarea NULL daca nu exista un astfel de element.

În functia cauta, pentru a afla numarul de ordine al unui student în tabela, se scade din adresa
elementului de tablou corespunzator acestuia (t) adresa de început a tabelei (tab).

Pentru ca programul sa functioneze corect, comenzile f si l trebuie date dupa ce în prealabil s-au dat
comenzile n si respectiv r. (Se utilizeaza pentru cautarea unui student o functie de cautare binara, deci
tabela trebuie sa fie anterior sortata!)

11

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