Pointeri
Operatori specifici pointerilor (IMPORTANT)
Memoria RAM este cea în care se încarcă un program pe care urmează să îl executăm. Ea este o succesiune de octeți (bytes), numerotați. Numărul de ordine al unui byte reprezintă adresa lui și de regulă este reprezentat în baza 16, de exemplu: 0x7ffa181a2d64. Orice variabilă ocupă în memorie un anumit număr de bytes consecutivi (în funcție de tipul variabilei); adresa primului octet alocat unei variabile reprezintă adresa variabilei.
Succesiunea de bytes alocată unui program se împarte în 4 secțiuni (de fapt sunt mai multe, dar pentru ce studiem la informatică relevante sunt doar 4) de memorie în care găsim memorate variabilele programului
- secțiunea de text: ține codul programului instrucțiune cu instrucțiune
- secțiunea de date: în general aici găsiți variabilele globale (cele declarate în afara funcțiilor)
- secțiunea stivă: Stiva este o regiune dinamică în cadrul unui proces, fiind gestionată automat de compilator. Stiva este folosită pentru a stoca “stack frame-uri”. Pentru fiecare apel de funcție se va crea un nou “stack frame”. La fel pentru fiecare variabilă declarată în interioriul unei funcții (inclusiv
int main()) se alocă frame în interiorul stivei. Un “stack frame” conține:- variabile locale
- argumentele funcției
- adresa de retur Pe marea majoritate a arhitecturilor moderne stiva crește în jos (de la adrese mari la adrese mici) și heap-ul crește în sus. Stiva crește la fiecare apel de funcție și scade la fiecare revenire din funcție.
- secțiunea heap: folosită în cadrul alocării dinamice a memoriei și nu este gestionat de compilator
Ca lectură suplimentară, dacă sunteți curioși, vă recomand articolul Anatomy of a Program in Memory
Un pointer este o variabilă care are ca valori adrese ale altor variabile, sau mai general adrese de memorie. O variabilă de tip pointer este o variabilă a cărei valoare este adresa altor variabile. Un pointer este asociat unui tip de variabile, deci avem pointeri către int, char, float, etc. Nu confundați adresa unei variabile cu valoarea memorată de aceasta. Ele sunt de regulă diferite, chiar și în cazul pointerilor!
În general o variabilă pointer p către tipul T se declară: T *p;. Un tip pointer la tipul T are tipul T*.
Exemple:
int j,*pj;/*pj este o variabila de tip pointer la întregi*/
char c, *pc;
Se introduc doi noi operatori:
- operatorul de adresare
&- aplicat unei variabile furnizează adresa acelei variabile
pj=&j; /* iniţializare pointer */
pc=&c;
Aceste iniţializări pot fi făcute la definirea variabilelor pointeri:
int j, *pj=&j;
char c, *pc=&c;
O greşeală frevent comisă o reprezintă utilizarea unor pointeri neiniţializaţi.
int *px;
*px=5;/* greşit, pointerul px nu este iniţializat (legat la o adresă de variabilă întreagă) */
Pentru a evita această eroare vom iniţializa în mod explicit un pointer la NULL, atunci când nu este folosit.
- operatorul de indirectare (dereferenţiere)
*– permite accesul la o variabilă prin intermediul unui pointer. Dacă p este un pointer de tipT*, atunci*peste obiectul de tipTaflat la adresap. În mod evident avem:
*(&x) = x;
&(*p) = p;
Exemplu:
int *px, x;
x=100;
px=&x; // px contine adresa lui x
cout << *px; // se afiseaza 100
Dereferenţierea unui pointer neiniţializat sau având valoarea NULL conduce la o eroare la execuţie.
Pointeri generici (Opțional)
Pentru a utiliza un pointer cu mai multe tipuri de date, la declararea lui nu îl legăm de un anumit tip.
void *px; // pointerul px nu este legat de nici un tip
Un pointer nelegat de un tip nu poate fi dereferenţiat. Utilizarea acestui tip presupune conversii explicite de tip (cast). Exemplu:
int i;
void *p;
. . .
p=&i;
*(int*)p=5; // ar fi fost gresit *p=5
Operații aritmetice cu pointeri (IMPORTANT)
Asupra pointerilor pot fi efectuate următoarele operaţii:
- adunarea / scăderea unei constante
- incrementarea / decrementarea
- scăderea a doi pointeri de acelaşi tip
Prin incrementarea unui pointer legat de un tip T, adresa nu este crescută cu 1, ci cu
valoarea sizeof(T) (despre sizeof puteți citi aici)
care asigură adresarea următorului obiect de acelaşi tip.
În mod asemănător, p + n reprezintă de fapt p + n*sizeof(T)
Doi pointeri care indică elemente ale aceluiaşi tablou pot fi comparaţi prin relaţia de
egalitate sau ne egalitate, sau pot fi scăzuţi.
Pointerii pot fi comparaţi prin relaţiile == şi != cu constanta simbolică NULL.
Legătura dintre vectori și pointeri (FOARTE IMPORTANT)
Între pointeri şi tablouri există o legătură foarte strânsă. Orice operaţie realizată folosind
variabile indexate se poate obţine şi folosind pointeri.
În C numele unui tablou este un pointer constant la primul element din tablou: x=&x[0]
Numele de tablouri reprezintă pointeri constanţi, deci nu pot fi modificaţi ca pointerii
adevăraţi. Exemplu:
int x[10], *px;
px=x; /* sunt operatii permise */
px++;
x=px; /* sunt operatii interzise, deoarece x este */
x++; /* pointer constant */
Prin urmare variabilele indexate pot fi transformate în expresii cu pointeri şi avem echivalenţele: