Pokazivač (programiranje)

S Vikipedije, slobodne enciklopedije

Pokazivač (ponekad pointer, prema engl. pointer) predstavlja promjenljivu specijalnog tipa u nekim programskim jezicima poput C-a, C++-a, Paskala itd. Pokazivač ima svrhu da čuva adresu memorijske lokacije neke druge promjenljive, konstante ili pokazivača.

Osnovna svojstva pokazivača su:

  • pokazivač može mijenjati vrijednost (tj. može pokazivati na razne lokacije za vrijeme svog radnog vijeka)
  • preko njega se može dobiti vrijednost promjenljive čiju adresu čuva (na koju pokazuje)
  • preko njega se može mijenjati vrijednost promjenljive na koju pokazuje

Kada uzimamo ili mijenjamo vrijednost elementa na kojeg pokazivač pokazuje, kažemo da ga dereferenciramo.

Tipovi pokazivača[uredi | uredi izvor]

U većini programskih jezika koji podržavaju pokazivače, pokazivači se dijele na tipske i bestipske.

Uz tipske pokazivače se veže informacija o tipu promjenljivih na koje će dati pokazivač moći pokazivati, odnosno čije će adrese moći čuvati. Kada deklarišemo tipski pokazivač, on do kraja svog vijeka ima isti tip.

Bestipski pokazivači nemaju određen tip promjenljive na koje će moći pokazivati, te mogu pokazivati na sve promjenljive podjednako. Na uštrb toga, međutim, postoje određena ograničenja vezana za ovu vrstu pokazivača:

  • nije dozvoljeno dereferenciranje (vidjeti poglavlje „Referenciranje i dereferenciranje“, niže)
  • nije dozvoljena pokazivačka aritmetika u jezicima u kojima je podržana za tipske pokazivače

Bestipski pokazivači se najčešće koriste u situacijama kada određena funkcija prihvata podatke promjenljivog tipa ili kada nam tip podatka nije unaprijed poznat.

Referenciranje i dereferenciranje[uredi | uredi izvor]

Referenciranje je proces u kojem pokazivaču dodjeljujemo adresu određene memorijske lokacije. Nakon toga kažemo da pokazivač pokazuje na tu memorijsku lokaciju.

Dereferenciranje je proces u kojem preko pokazivača koji već pokazuje na neku memorijsku lokaciju pristupamo samoj lokaciji, bilo radi čitanja njene vrijednosti, bilo radi njenog mijenjanja. Zabranjeno je, i najčešće uzrokuje prekid rada programa, dereferenciranje pokazivača koji:

  • sadrži vrijednost koja služi za označavanje da pokazivač ni ne pokazuje ni na šta (najčešće vrijednost 0)
  • pokazuje na adresu koja se ne nalazi u memorijskom prostoru procesa koji izvršava program. To su najčešće pokazivači sa neinicijalizovanim vrijednostima, koji sadrže adresu koja se slučajno zatekla u prostoru zauzetom za dati pokazivač.

Pokazivačka aritmetika[uredi | uredi izvor]

Pokazivačka aritmetika se odnosi na poseban način vršenja računskih operacija sabiranja i oduzimanja kada među operandima ima i pokazivača. Tada često ne važe uobičajena pravila pri računanju, tj. adrese koje pokazivači čuvaju neće se sabirati i oduzimati kao obični brojevi. Pokazivačka aritmetika za svrhu ima sofisticirano rukovanje pokazivačima kako bi se dobili određeni rezultati. Pomoću pokazivačke aritmetike, pokazivač se može kretati po određenim memorijskim lokacijama, mogu se mjeriti udaljenosti jedne memorijske lokacije od druge u zavisnosti od tipova pokazivača itd.

Pokazivači u programskom jeziku C[uredi | uredi izvor]

Tipovi pokazivača[uredi | uredi izvor]

U programskom jeziku C, pokazivači se dijele na tipske i bestipske.

Pored pokazivača na obične promjenljive, postoje i pokazivači na funkcije. Pokazivači na funkcije se mogu prosljeđivati drugim funkcijama kao argumenti i preko njih se mogu pozivati funkcije na koje pokazuju, što im je i primarna primjena.

Deklaracija[uredi | uredi izvor]

Tipski pokazivači se deklarišu na sljedeći način:

tip_pokazivaca * ime_pokazivaca;

Prvo se navodi tip promjenljive na kakve će moći pokazivati pokazivač, zatim slijedi zvjezdica (*) i na kraju ime pokazivačke promjenljive terminirane tačka-zarezom. Za ime pokazivača važe uobičajena pravila imenovanja u p. j. C.

Bestipski pokazivači se deklarišu na sljedeći način:

void * ime_pokazivaca;

Umjesto tipa ovde se navodi ključna riječ void, nakon koje važe pravila kao za tipske pokazivače.

Pokazivači na funkcije se deklarišu na sljedeći način:

tip_rezultata_originalne_funkcije (*ime_pokazivaca)(tip_prvog_argumenta_funkcije, tip_drugog_argumenta_funkcije, ...);

Slijedi primjer za deklaraciju pokazivača na određenu funkciju:

/* декларација произвољне функције */
char * kopiraj(char * odrediste, char * izvoriste )
{
    /* блок функције */
}

/* декларација показивача на овакву функцију (без иницијализације) */
char * (*pkopiraj)(char * odrediste, char * izvoriste );

Referenciranje[uredi | uredi izvor]

Pokazivač pokazuje na promjenljivu tako što čuva u sebi njenu adresu

Da bismo dodijelili određenu vrijednost pokazivačkoj promjenljivoj, koristimo uobičajen operator dodjele:

p1 = <<adresa>>;

Pošto je svrha pokazivača da čuvaju adresu nekog drugog podatka, moramo da:

  • znamo unaprijed na kojoj tačno lokaciji se nalazi odgovarajuća promjenljiva
  • koristimo operator & da bismo dobili adresu date promjenljive
  • pozovemo neku od ugrađenih funkcija koje nam alociraju novu memoriju i vraćaju adresu alociranog prostora

Slijede primjeri za svaku od stavki, redom:

int * p1;
int a = 3;
p1 = 0x4CC7EC5C; /* изузетно је опасно и не препоручује се додјељивати адресе директно */
p1 = &a; /* користимо оператор & за добијање адресе од a да бисмо је додијелили промјенљивој p1 */
p1 = malloc(sizeof(int) ); /* користимо функцију malloc да алоцирамо простор величине једне цјелобројне промјенљиве. */
                               /* malloc враћа адресу новоалоцираног простора */

U slučajevima kada se želi pokazati da pokazivač ne čuva nikakvu adresu, tada mu dodjeljujemo nulu pomoću ugrađene konstante NULL:

int * p1 = NULL;

Kod pokazivača na funkcije važi nešto drugačija sintaksa referenciranja. Ako postoji funkcija f, i pokazivač pf deklarisan tako da može na nju da pokazuje, onda se referenciranje vrši jednostavnom dodjelom promjenljive f promjenljivoj pf, bez korišćenja običnih zagrada, kao kod samog poziva:

pf = f;

Dereferenciranje[uredi | uredi izvor]

Dereferenciranje je proces u kojem preko pokazivača koji već pokazuje na neku memorijsku lokaciju pristupamo samoj lokaciji. Za ovu operaciju se koristi operator zvjezdice (*) ispred imena pokazivačke promjenljive:

int * p1;
int a;
p1 = &a; /* p1 нека показује на a */
*p1 = 10; /* оно на шта p1 показује (a), нека постане једнако 10 */

Pokazivači na funkcije se deferenciraju na nešto drugačiji način. Zapravo, kod njih se ne može govoriti o pravom dereferenciranju, nego radije samo o pozivanju funkcije preko pokazivača. Naime, onog trenutka kada je npr. pokazivač pf počeo da pokazuje na funkciju f, on je postao alias (lažno ime, drugo ime) za funkciju f, i koristi se ravnopravno, na isti način kao i sama funkcija. Slijedi primjer:

int saberi(int a, int b )
{
    return a + b;
}

int main()
{
    int (*psaberi)(int, int );
    int x = 3, y = 4;
    int z;
    psaberi = saberi;
    z = saberi(x, y );    /* позив оригиналне функције */
    z = psaberi(x, y );   /* позив функције преко показивача */
}

Slijedi primjer prosljeđivanja pokazivača na funkciju drugoj funkciji, koja je poziva preko tog pokazivača:

int saberi(int x, int y )
{
    return x + y;
}

int pozovi(int (*psaberi)(int, int), int x, int y )
{
    return psaberi(x, y );       /* позивамо функцију преко показивача, и просљеђујемо јој преостала два аргумента */
}

int main()
{
    int z;
    z = pozovi(saberi, 3, 4 );   /* први аргумент је само име функције, без заграда, а други су аргументи за сабирање */
    return 0;
}

Pokazivačka aritmetika[uredi | uredi izvor]

U pokazivačkoj aritmetici programskog jezika C ne važe uobičajena pravila pri računanju, tj. adrese koje pokazivači čuvaju neće se sabirati i oduzimati kao obični brojevi. Način računanja će zapravo zavisiti od toga koji je tip promjenljive na koju pokazivači pokazuju, odnosno koji je tip samih pokazivača. Sabiranje i oduzimanje pokazivača sa cijelim brojem se vrši po sljedećoj formuli:

p1 ± n = p1 ± (n * sizeof(*p1));

Pogledajmo sljedeći primjer:

double x = 3.14; /* претпоставимо да се x налази на адреси 10000 */
double * px = &x; /* px добија вриједност 10000 */
printf("%d\n", px + 5 ); /* штампа се број 10000 + 5*sizeof(double) = нпр. 10000 + 5*8 = 10040 (не 10005) */

Pokazivači se mogu i međusobno oduzimati, kada važe slična pravila kao kod pokazivača i cijelog broja.

Važe sljedeća restriktivna pravila vezana za pokazivače:

  • pokazivači se ne smiju množiti niti dijeliti cijelim brojem
  • pokazivači se ne smiju međusobno sabirati, množiti niti dijeliti

Primjena[uredi | uredi izvor]

U programskom jeziku C, najvažnije primjene pokazivača su sljedeće:

  • prenos argumenata funkciji po referenci
  • implementacija nizova
  • implementacija dinamičkih struktura

Prenos argumenata funkciji po referenci[uredi | uredi izvor]

U programskom jeziku C, funkcije su takve da dobijaju uvijek kopije argumenata koje joj prosljeđujemo, a nikad originalne argumente. Na ovaj način funkcija ne može imati spoljašnje efekte jer sve što dobija od funkcije-pozivaoca su kopije originalnih argumenata koji ostaju netaknuti. Ovakav način prenosa argumenata se popularno naziva prenos argumenata po vrijednosti, jer funkcija dobija praktično samo vrijednosti originalnih argumenata ali nikad i direktno njih same.

Problemi se javljaju, međutim, u slučajevima kada želimo da funkcija ipak izmijeni određene promjenljive koje joj proslijedimo kao argumente. Ta se potreba javlja npr. kada želimo funkciju koja će imati više rezultata, jer pomoću uobičajene ključne riječi return ona može vratiti samo jedan rezultat. Tada koristimo tzv. prenos argumenata po referenci koji u stvari predstavlja praksu da umjesto originalnog elementa proslijedimo njegovu adresu kao argument. Ova adresa predstavlja pokazivač na originalni element, pa iako se opet poštuje pravilo da funkcija dobija kopiju argumenta koji joj se proslijedi, ovaj put ona dobija kopiju adrese, što je ipak sasvim dovoljno da se originalnom argumentu priđe direktno, dereferenciranjem.

Pogledajmo sljedeći primjer:

void povecaj(int c )
{
    c = c+1;
}

int main()
{
    int x = 1;
    povecaj(x );
    printf("%d", x );
    return 0;
}

Iz prethodnog paragrafa zaključujemo da funkcija povecaj neće izvršiti svoju ulogu jer na mjesto promjenljive c dobija kopiju od x. Na taj način kopija od x biva povećana za 1, ali ne i originalni element x u funkciji main. Da bismo popravili kôd, moraćemo izvršiti par izmjena:

  • funkcija povecaj treba da se prilagodi tako da prihvata adresu cjelobrojne promjenljive, a ne nju samu
  • kada dobije adresu, dereferenciraće je i povećati ono što se nalazi na toj adresi za 1
  • funkcija main treba da pošalje adresu od x, a ne sāmo x

Pogledajmo kako izgleda rezultat izmjena:

void povecaj(int * pc )
{
    *pc = *pc + 1;     /* *pc представља оно на шта pc показује, тј. оно што се налази на адреси pc */
}

int main()
{
    int x = 1;
    povecaj(&x );      /* &x представља адресу од x */
    printf("%d", x );
    return 0;
}

Koristeći prenos argumenata po referenci, programer takođe štedi na kopiranju argumenata pri pozivanju jedne funkcije iz druge. Ako je argument koji prenosimo velik podatak, npr. struktura od više elemenata čija ukupna veličina prevazilazi veličinu pokazivačke promjenljive na datom sistemu, dobro ga je proslijediti po referenci jer na taj način kopiramo manje podataka.

Implementacija nizova[uredi | uredi izvor]

Skica uređenja niza u memoriji

U programskom jeziku C ne postoje nizovi kao ugrađeni tipovi. Naprotiv, oni se implementiraju preko ostalih ugrađenih tipova i pokazivača. Ovo važi i za jednodimenzionalne i višedimenzionalne nizove (matrice, kocke itd.). Svaki niz i C-u se sastoji od dva fizička dijela - elemenata koji sačinjavaju niz i jednog pokazivača koji pokazuje na početak tog niza, tj. njegov prvi element (vidjeti sliku desno). Dakle, kada predstavimo niz na sljedeći način:

int a[10];

tada nastaje 10 elemenata niza (koji nijedan nema svoje pravo ime), i jedna pokazivačka promjenljiva a preko koje se pristupa svim elementima niza koristeći sintaksu a[0] (prvi element, jer u C-u indeksiranje kreće od nule, a ne od jedinice), a[1], a[2], itd.

Nizovi se u C-u implementiraju preko pokazivača zahvaljujući specifičnoj pokazivačkoj aritmetici. Ako je a pokazivač na prvi element niza, onda *a predstavlja sam element. Dalje, pošto pokazivačka aritmetika diktira da a+1 bude pokazivač na sljedeći element niza, onda će *(a+1) predstavljati sam drugi element niza. Idući dalje, primjećujemo da je a[n] ekvivalentno sa *(a+n). To je upravo ono što kompajler vidi kada putem operatora indeksa referenciramo određeni element niza.

Ako idemo dalje, i predstavimo matricu realnih brojeva М dimenzija m x n, tada je stanje u memoriji sljedeće:

  • M predstavlja pokazivač na prvi element niza od m pokazivača.
  • svaki od m pokazivača pokazuje na po prvi element niza od n realnih brojeva (vidjeti sliku desno).
Skica uređenja višedimenzionalnog niza (matrice) u memoriji

Kada koristimo statičke deklaracije nizova, ovakva stanja memorije se formiraju automatski, ali je takođe moguće formirati ih ručno, koristeći običnu deklaraciju pokazivača i neku od ugrađenih C-ovih funkcija za alociranje memorije. Slijedi primjer:

char ** alocirajMatricu(int m, int n )
{
    char ** M;
    int i;
    M = malloc(m * sizeof(char *) );        /* алоцирамо меморију за низ показивача */
    for (i = 0; i < m; i++ )                /* за сваки показивач у том низу алоцирамо по још један низ */
         M[i] = malloc(n * sizeof(char) );  /* карактера да бисмо формирали матрицу. */
    return M;                                /* враћамо показивач „на матрицу“. */
}

int main()
{
    char ** M;
    int m, n;
    int i, j;
    scanf("%d %d", &m, &n );         /* питамо корисника колика матрица је потребна */
    M = alocirajMatricu(m, n );      /* позивамо функцију да нам алоцира простор те величине */
    for (i = 0; i < m; i++ )         /* надаље се понашамо као према обичној матрици */
         for (j = 0; j < n; j++ )
               M[i][j] = 'x';

    // овако алоцирану матрицу морамо обрисати ручно
    for (i = 0; i < m; i++ )
         free(M[i] );
    free(M );
    return 0;
}

Implementacija dinamičkih struktura[uredi | uredi izvor]

Poseban članak: Dinamičke strukture podataka

Uopšteno, dinamičke strukture podataka predstavljaju tip strukture koja zauzima tačno onoliko memorije koliko joj je neophodno, i može se širiti i smanjivati po potrebi. Najčešće spominjane dinamičke strukture su binarno stablo (ili binarno drvo), lista, stek, graf, red, i drugi. U programskom jeziku C one se implementiraju koristeći ceovske strukture (struct), pokazivače i dinamičko alociranje memorije pomoću neke od ugrađenih funkcija C-a.

Pokazivači u programskom jeziku Paskal[uredi | uredi izvor]

Tipovi pokazivača[uredi | uredi izvor]

U programskom jeziku Paskal, pokazivači se dijele na tipske i bestipske.

Paskal ne podržava pokazivače na funkcije.

Deklaracija[uredi | uredi izvor]

Tipski pokazivači se deklarišu na sljedeći način:

ime_pokazivaca: ^tip_pokazivaca;

Prvo se navodi ime pokazivačke promjenljive i dvotačka (kao kod deklaracije ostalih elementarnih tipova promjenljivih u Paskalu), zatim slijedi kapica (^) i na kraju tip promjenljive na kakve će pokazivač moći pokazivati. Za ime pokazivača važe uobičajena pravila imenovanja u p. j. Paskal.

Bestipski pokazivači se deklarišu na sljedeći način:

ime_pokazivaca: pointer;

Umjesto tipa i kapice, ovde se navodi ključna riječ pointer.

Referenciranje[uredi | uredi izvor]

Referenciranje je proces u kojem pokazivaču dodjeljujemo adresu određene memorijske lokacije. Da bismo dodijelili određenu vrijednost pokazivačkoj promjenljivoj, koristimo jedan od sledeća dva načina:

  • uobičajen operator dodjele
  • komandu new radi alokacije memorije i istovremene dodjele adrese te memorije pokazivaču

Slijede primjeri za obje stavke, redom:

var
p1: ^integer;
a: integer;
begin
p1 := @a; (* користимо оператор @ за добијање адресе од a да бисмо је додијелили промјенљивој p1 *)
new(p1) (* користимо команду new да алоцирамо простор величине једне цјелобројне промјенљиве (у складу са декларацијом показивача) *)
end.

U slučajevima kada se želi pokazati da pokazivač ne čuva nikakvu adresu, tada mu dodjeljujemo nulu pomoću ugrađene konstante nil:

p := nil;

Dereferenciranje[uredi | uredi izvor]

Za ovu operaciju se koristi operator kapice (^) iza imena pokazivačke promjenljive:

var
p1: ^integer;
a: integer;
begin
p1 := @a; (* p1 нека показује на a *)
p1^ := 10; (* оно на шта p1 показује (a), нека постане једнако 10 *)
end.

Pokazivačka aritmetika[uredi | uredi izvor]

Pokazivačka aritmetika nije podržana u standardnom Paskalu. Postoje pojedine ekstenzije Paskala koje dozvoljavaju pokazivačku aritmetiku, kao što je GPC (Gnu-ova ekstenzija Paskala). Pokazivačka aritmetika Paskala je i tada jednostavna i ograničena. Kod nje važe uobičajena pravila pri računanju, tj. adrese koje pokazivači čuvaju će se sabirati i oduzimati kao obični brojevi, na taj način dodajući i oduzimajući tačno zadati broj bajtova od adrese koju pokazivač čuva.

Pogledajmo sljedeći primjer:

var
x: real; (* претпоставимо да се x налази на адреси 10000 *)
px: ^real;
{$X+} (* коришћење екстензије која дозвољава показивачку аритметику *)
begin
x := 3.14;
px := @x; (* px добија вриједност 10000 *)
px := px+5; (* px добија вредност 10005 *)
end.

Komande inc i dec služe da povećaju odnosno smanje vrijednost pokazivača za onoliko bajtova koliko zauzima promjenljiva na koju pokazuje. Dakle, komande inc i dec se vrše po sledećim formulama:

inc(p1) <=> p1 := p1 + sizeof(^p1);
dec(p1) <=> p1 := p1 - sizeof(^p1);

Ovo obezbjeđuje npr. iteriranje po nizu služeći se jednim pokazivačem.

Primjena[uredi | uredi izvor]

U programskom jeziku Paskal, pokazivači nemaju preveliku primjenu, za razliku od programskog jezika C. Paskal sadrži niz kao ugrađeni tip (koristeći ključne riječi array ... of), prenošenje argumenta funkciji po referenci je obezbijeđeno sintaksom jezika (koristeći ključnu riječ var), te je jedina primjena pokazivača u Paskalu najčešće samo implementacija dinamičkih struktura podataka.

Pokazivači u ostalim programskim jezicima[uredi | uredi izvor]

Ada[uredi | uredi izvor]

Ada podržava samo tipske pokazivače i konverzija između pokazivača različitih tipova je podržana samo u određenom broju slučajeva. Svi pokazivači se podrazumijevano inicijaliziju na nulu (null), i dereferenciranje pokazivača sa tom vrijednošću izaziva izuzetak. U programskom jeziku Ada, pokazivači se nazivaju pristupni tipovi, i ne podržavaju pokazivačku aritmetiku, bez korišćenja ekstenzija (System.Storage_Elements)

C++[uredi | uredi izvor]

C++, kao jezik nastao na osnovu C-a, podržava cjelokupnu sintaksu C-a vezanu za pokazivače.

C#[uredi | uredi izvor]

U programskom jeziku C#, pokazivači su podržani ali samo pod određenim uslovima: bilo koji blok koda koji sadrži pokazivače mora biti označen kao „nesiguran“, koristeći ključnu riječ unsafe. Programi ili moduli koji sadrže takve blokove obično zahtijevaju više nivoe dozvola od strane korisnika da bi se pokrenuli. Sintaksa je jako slična sintaksi programskog jezika C.

Fortran[uredi | uredi izvor]

Fortran podržava pokazivače od verzije 90. Pokazivači u Fortranu, međutim, predstavljaju složene tipove podataka, koji pored adrese ciljne promjenljive takođe sadrže i donju i gornju granicu niza (ukoliko pokazivač služi niz), podatke o sekcijama niza i druge pomoćne podatke.

Vidi još[uredi | uredi izvor]

Spoljašnje veze[uredi | uredi izvor]