Você está na página 1de 22

Osnove Raunarstva Analiza devetog tutorijala

Akademska 2014/2015
Enil Paji

OSNOVE RAUNARSTVA
Analiza tutorijala br. 9
(sa povrnim uvodom u dinamiku alokaciju memorije)

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji
Tutorijal 9 prati gradivo iz oblasti pokazivaa te oblasti koje su zahvaene pokazivaima.
Ovdje emo uvesti pojam pokazivaa tj. pokazivakog tipa, pojmove referenciranja odnosno
dereferenciranja te pravila pokazivake aritmetike, tj. aritmetike nad pokazivakim
varijablama. U izlaganju za ovu analizu biti e ponekad referencirani dijelovi iz dodatka za
predmet Tehnike programiranja Sve to trebate znati o pokazivaima u jeziku C++ iji je
autor prof. dr. eljko Juri. Bie analizirano nekoliko jednostavnih primjera kako bismo
uvidjeli na konkretnim stvarima mogunosti i eventualne probleme pokazivaa.

Pokazivake varijable nisu nita drugo nego varijable koje sade adresu neke druge varijable.
To je specijalna vrsta varijabli nad kojima moemo primijeniti odreen broj operatora, meu
njim i operator dereferenciranja kojeg emo kasnije objasniti. Pokazivaka varijabla (u
nastavku samo pokaziva) sadri adresu nekog drugog objekta, tada kaemo da ona pokazuje
na taj objekat. Ve smo spominjali na koji nain su varijable smjetene u memoriji i tom
prilikom smo objasnili da je svaka varijabla u memoriji smjetena na odreenoj (unikatnoj)
adresi. Pokazivai kao svoj sadraj (odnosno vrijednost) imaju adresu na kojoj je smjetena
neka varijabla. Zbog navedenog, mi moemo indirektno pristupiti toj varijabli, jer znamo
adresu na kojoj je ona smjetena. Pokazivai su jedini nain u jeziku C da ostvarimo
indirektni pristup drugim varijablama. Ta mogunost indirektnog pristupa predstavlja veliku
fleksibilnost u to emo se uvjeriti kasnije.
Moemo imati pokazivae na sve varijable koje moemo deklarirati u jeziku C, to znai da i
pokazivai na neki nain imaju svoj tip. Znai, moemo imati pokaziva na varijablu tipa
int, na varijablu tipa double, na niz (vidjeemo kasnije da je to pokaziva na prvi elemenat
niza, ali emo takoer vidjeti da moemo imati i pokaziva na niz kao cjelinu). Mogue je
imati pokaziva na varijablu koja je takoer pokaziva. Sve navedeno emo objasniti neto
kasnije.
Pokaimo sada kako bismo deklarirali pokaziva odnosno pokazivaku varijablu. Sintaksa je
slina deklaraciji obine varijable, navodimo tip pa ime. Razlika je da kod deklaracije
pokazivaa dodajemo znak zvijezda * prije imena varijable. Pokaimo generalnu sintaksu
deklaracija pokazivake varijable:
TIP *pokazivac;

Kao to vidimo, ovdje varijabla pokazivac nije tipa TIP nego pokaziva na TIP. Prikaimo
primjer konkretne deklaracije pokazivakih varijabli. Varijable pok1, pok2, pok3, pok4, pok5
redom predstavljaju pokazivae na tipove int, char, double, float, unsigned int:
int *pok1;
char *pok2;
double *pok3;
float *pok4;
unsigned int *pok5;

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji
Naravno, kako su i pokazivai varijable i oni mogu biti neinicijalizovani. To je sluaj sa svim
iznad deklariranim pokazivaima, oni nisu inicijalizovani, tj. njhova vrijednost je
nedefinisana. Vidjeemo kasnije opasnosti neinicijalizovanih pokazivaa. Pokazivai su
varijable u koje smjetamo adrese drugih varijabli i nita drugo! Tako da ni u kojem sluaju
ne bismo trebali pokazivaima dodjeljivati brojevne vrijednosti:
int *pok = 100; /* NE TREBA PISATI OVAKO */

Naime, komapajler za jezik C e ovo dozvoliti, ali e nas upozoriti da takvo neto vjerovatno
predstavlja greku: C:\....\main.c|6|warning: initialization makes pointer from integer
without a cast [enabled by default]|. Dok kod kompajlera za jezik C++ ovakva dodjela
uopte nije mogua i bie javljena greka! Znai, nikada ne bismo trebali raditi ovakve
dodjele (osim eventualno dodjele NULL konstante ije emo znaenje objasniti kasnije).
Znai, pokaziva smije sadravati samo adrese i to adrese varijabli koje su deklarisane u
programu (eventualno i adresu dinamiki alocirane memorije to emo spomenuti na kraju
dokumenta). Pokazivau treba uvijek dodjeljivati adresu uz pomo adresnog operatora &
kojeg emo objasniti u nastavku.
Da bismo inicijalizirali (ili izvrili dodjelu) pokaziva moramo imati ili ve postojeu
varijablu koja se nalazi na nekoj adresi ili dinamiki zauzeti dio memorije to nije tema ove
analize ali emo na kraju i to spomenuti. Primjer inicijalizacije pokazivaa adresom ve
postojee varijable:
int var = 100;
int *pok = &var;

Ovjde moemo uoiti i prisustvo adresnog operatora (&) kojeg smo primijenili na varijablu
var. Adresni operator ima znaenje adresa od i njime mi dobivamo adresu neke varijable.
Ve smo se sa ovim operatorom susretali kod pozivanja scanf funkcije gdje smo prosljeivali
adrese varijabli (u cilju da ih scanf funkcija izmijeni, kasnije emo i mi pisati funkcije koje
mijenjaju varijable na proslijeenim adresama). Ovaj operator se jo naziva i operator
referenciranja sa kojim mi varijablu veemo za pokaziva. Adresni operator moemo
primijeniti jedino na varijable (odnosno l-vrijednosti koje smo spominjali), tako da je
besmisleno pisati &100 ili &(var + 7).
Nakon to smo povezali pokaziva i varijablu (tj. na to on pokazuje), mi sada moemo
indirektno pristupati varijabli. Ovo dvoje ispod je isto:
var = 250;
/* Isto kao i ovo: */
*pok = 250;

U isjeku iznad moemo primijetiti jedan novi operator. To je operator dereferenciranja sa


kojim mi indirektno pristupamo varijabli var, tj. pristupamo onome na to pokaziva pok
pokazuje (pristupamo pokazivanom objektu, engl. pointee). Operator dereferenciranja je
unarni operator, znai primjenjuje se nad jednim operandom, tj. pokazivaom. Oznaava se
3

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji
sa zvjezdicom * i pie se prije imena pokazivaa. Dereferencirani pokaziva se moe
upotrijebiti u bilo kojem kontekstu gdje se moe upotrijebiti i obina varijabla, pa je
dozvoljeno sljedee:
printf ("%i", *pok);
*pok = 10;
*pok = *pok + 4;
int broj = *pok - 10;

Naravno, ovo vrijedi samo za dereferencirani pokaziva (koji predstavlja varijablu), dok nema
smisla pisati npr. sljedee:
int broj = pok * 4 - 10;

Konstrukcija iznad se nee moi ni kompajlirati jer nije definisano mnoenje pokazivaa, to
emo vidjeti kasnije. Ipak, da smo dereferencirali pokaziva, izraz iznad bi bio legalan, npr.
da smo napisali sljedee: int broj = *pok * 4 - 10;.
Ovdje je zvjezdica * kod pokazivaa napisana podebljano u cilju isticanja da se radi o
unarnom operatoru dereferenciranja a ne binarnom operatoru mnoenja.
Treba napomenuti veliku razliku izmeu deklaracije pokazivaa i njegovog koritenja
(dereferenciranja). Naime, kada mi deklariramo pokaziva, npr. int *pok , tada se znak
zvijezda * koristi da bismo naglasili da se radi o pokazivakoj a ne obinoj varijabli. Prilikom
dereferenciranja, npr. *pok = 10, znak zvjezdica * ima posve drugo znaenje, i predstavlja
indirektan pristup pokazivanom (pointee) objektu. Vano je da studenti ne mijeaju ovo
dvoje.
Sada da se dotaknemo i stila pisanja deklaracij pokazivakih promjenljivih. Naime, kako
kompajler za jezik C (kao i C++) ignorira sve razmake koji ne predstavljaju osnovne razmake
(koji tu moraju biti, npr. prilikom pisanja deklariranja varijable: double b, razmak je
neophodan, jer ukoliko ga uklonimo dobili bismo rijei doubleb koja nee biti prepoznata) mi
moemo pisati, naizgled, razliite vrste konstrukcija. Pogledajmo sljedee:
int* pok1, pok2, pok3;

Kojeg tipa su varijable pok1, pok2 i pok3? Studenski odgovor na ovo pitanje je bio da su sve
varijable pokazivai. To nije istina, jer je ovdje samo varijabla pok1 pokaziva na tip int tj.
tipa int *, dok su varijable pok2 i pok3 tipa int. Isjeak iznad je ekvivalentan sljedeem:
int *pok1;
int pok2, pok3;

Zbog toga mi preporuujemo da se koristi stil pisanja kojeg smo do sada ve koristili.
Pogledajmo sada sljedeu konstrukciju.
int *pok1, pok2, pok3;

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji
Ovdje se vidi da je varijabla pok1 pokaziva, a ostale dvije su obine cjelobronjne varijable
tipa int.
Problem sa iznad napisanom konstrukcijom jeste to se iz nje ne vdi da int * predstavlja tip (i
zvjezdica * predstavlja dio tipa). Poenta je da su iznad opisani stilovi funkcionalno
ekvivalentni, a koji stil koristiti je stvar programera. Pisanje konstrukcija poput int *p je
vie u duhu jezika C gdje se ita *p je tipa int, dok je pisanje int* p vie u duhu jezika
C++ gdje se precizno definira tip i ita se p je tipa int *. Kako god, vano je biti
konzistentan i drati se jednog stila do kraja...
Moe se postaviti pitanje: kako emo u jednoj naredbi deklarirati vie pokazivakih varijabli?
Odgovor je jednostavan, svakoj varijabli moramo dodati zvjezdicu:
int *pok1, *pok2, pok3, *pok4, pok5;

Varijable pok1, pok2 i pok4 su pokazivai (tj. tipa int *), dok su varijable pok3 i pok5 obine
varijable cjelobrojnog tipa (tj. tipa int).
Sve nekonstantne varijable za vrijeme svoga ivota mogu mijenjati svoj sadraj, pa tako i
pokazivai. Oni mogu prvo pokazivati na jednu varijablu pa kasnije na drugu. Pogledajmo
primjer:
int x = 10, y = 15, *pok;
pok = &x;
printf ("*pok = %i\n", *pok); /* Ispisuje 10 */
pok = &y;
printf ("*pok = %i\n", *pok); /* Ispisuje 15 */

Varijable x i y su tipa int, dok je varijabla pok tipa pokaziva na int, tj. tipa int *. Prvo smo
pokazivau dodijelili adresu varijable x pa smo ispisali njenu vrijednost preko *pok (to je
isto kao i x). Nakon toga smo pokazivau dodijelili adresu varijable y te ispisali njenu
vrijednost.
Ve smo ranije spominjali da u jeziku C postoji samo jedan nain da funkcija promijeni
vrijednost argumenta kojeg smo proslijedili. Taj nain je sljedei: funkciji emo kao
argumente proslijediti adresu varijable, te emo na taj nain indirektno pristupiti varijabli i
izmijeniti je.
Primjer 1: Napisati funkciju koja e vriti zamjenu proslijeenih joj realnih argumenata.
Testirati napisanu funkciju.
Napiimo sada funkciju Zamijeni koja e vriti zamjenu dvije varijable, tj. zamjenu njihovih
vrijednosti. Funkcija treba imati takve parametre da oni mogu prihvatiti adrese varijabli, a ve
smo ranije rekli da se adrese varijabli mogu smjestiti u pokazivae. Znai funkcija treba kao
parametre imati pokazivae na neki tip. Ona e imati parametre tipa double * jer je u postavci
reeno da se vri zamjena vrijednosti dvije realne varijable. Pri rjeavanju emo funkciju
napisati nakon main funkcije, kako bismo se ponovo podsjetili na pisanje prototipa:
5

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji
#include <stdio.h>
void Zamijeni (double *, double *); /* Prototip funkcije */
int main ()
{
double br1 = 100.4, br2 = 40.77;
printf ("Prije poziva funkcije: \n\n\t br1 = %.3f \n\t br2 =
%.3f\n", br1, br2);
Zamijeni (&br1, &br2); /* Prosljedjujemo adrese */
printf ("\nNakon poziva funkcije: \n\n\t br1 = %.3f \n\t br2 =
%.3f\n", br1, br2);
return 0;
}
void Zamijeni (double *pok_br1, double *pok_br2)
{
double temp = *pok_br1;
*pok_br1 = *pok_br2;
*pok_br2 = temp;
}

Funkcija Zamijeni nije trebala da vraa nita, pa smo joj kao povratni tip stavili tip void.
Prilikom njenog pozivanja proslijedili smo adrese od varijabli koje trebamo zamijeniti. Unutar
funkcije smo koristili standardnu proceduru zamjene dvije varijable. Poto su parametri
funkcije pok_br1 i pok_br2 pokazivai, da bismo pristupili onome emu oni pokazuju morali
smo ih dereferencirati.
Podsjetimo se jo zato nismo navodili imena parametara u prototipu funkcije. Naime, kako
smo ve ranije naglasili, imena parametara u prototipu funkcije nam ne koriste pa ih moemo
izostaviti. Naravno, tipovi tih parametara se nisu smjeli izostaviti. Iz samog prototipa funkcije
moemo vidjeti da je double * tip, znai tip parametra nije double nego double *, tj. zvjezdica
je sastavni dio tipa. Ovo e biti vano kasnije kada budemo spominjali konverziju
pokazivaa.
Nakon kompajliranja i pokretanja programa, dobiti emo neto ovako:

Prikaimo sada i pogrean nain pisanja Zamijeni funkcije, kojeg napiu manje iskusni
studenti koji nisu u potpunosti razumjeli razliku izmeu pokazivaa i onoga na to on
pokazuje.
void Zamijeni (double *pok_br1, double *pok_br2)
{
double *temp = pok_br1; /* POGRESNO! */
pok_br1 = pok_br2;

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji
pok_br2 = temp;
}

U isjeku iznad je uraene zamjena pokazivaa a ne onoga na to pokazivai pokazuju. Poto


znamo da su parametri funkcije zapravo lokalne varijable (vidljive samo unutar funkcije) oni
e samo zamijeniti svoje vrijednosti tj. adrese ali nee promijeniti vrijednosti onoga na to
pokazuju. Efekat funkcije iznad jeste da na njenom kraju pok_br2 pokazuje na ono to je
pokazivao pok_br1 i obrnuto, ali vrijednosti varijabli nisu izmijenjene!
U ovom (pogrenom) isjeku iznad smo napravili jednu do sada neopisanu stvar: meusobno
smo vrili dodjelu pokazivaa. To je dozvoljeno ako su pokazivai istog tipa, tj. pokazuju na
isti tip. Ovo nam govori da moemo imati vie pokazivaa koji pokazuju na jednu te istu
varijablu, pogledajmo primjer:
int var = 10;
int *pok1 = &var;
int *pok2 = pok1; /* Dodjela pokazivaca */
printf ("*pok1 = %i\n", *pok1); /* Ispisuje 10 */
*pok2 = 77;
printf ("*pok1 = %i\n", *pok1); /* Ispisuje 77 (opet *pok1 ispisujemo) */

Pokaimo jo jedan neispravan nain pisanja Zamijeni funkcije:


void Zamijeni (double *pok_br1, double *pok_br2)
{
double temp = *pok_br1;
pok_br1 = pok_br2;
*pok_br2 = temp;
}

Ukoliko, sa istom main funkcijom kao kod ispravnog primjera, ovo kompajliramo i
pokrenemo, dobiemo ovakav izlaz:

Program e kompajlirati, ali nee raditi ispravno. Napravljena je jedna podmukla greka gdje
smo mi u pok_br1 smjestili vrijednost pok_br2 (zaokruena linija), tj. manipulirali smo
vrijednostima pokazivaa a ne vrijednostima onoga na to oni pokazuju. Primjer bi radio da
smo u zakruenoj liniji pisali ovo: *pok_br1 = *pok_br2;.

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji
Do prave izraajne moi pokazivai dolaze kada se primijenjuju nad nizovima jer, kako smo
ve objanjavali, elementi niza su smjeteni jedan za drugim u memoriji. Pod pokazivaem na
niz se zapravo misli na pokaziva koji pokazuje na prvi elemenat niza. Deklaracija je
sljedea:
int niz[] = {7, 5, 0, 3, 7, 2};
int *pok = &niz[0];

Sada varijabla pok pokazuje na poetak niza (kae se pokazuje na niz) te sljedea konstrukcija
e izmijeniti prvi elemenat niza (tj. elemenat sa indeksom 0):
*pok = 10;

Radi jednostavnosti pretpostavimo da se adresira po bajtima, tj. da svaka memorijska elija


koja ima adresu zauzima jedan bajt, tj. 8 bita (mogui su i drugi naini adresiranja). U tome
sluaju e niz iznad biti predstavljen ovako:

...

...
1Byte

1Byte

0x100

0x104

0x108

0x112

0x116

0x120

...

...

1Byte

Prvi elemenat neka je na fiktivnoj adresi 0x100, sljedei elemenat e biti na adresi 0x104
poto na veini kompajlera i platformi tip int zauzima 32 bita, odnosno 4 bajta. Znai, prvi
elemenat niza je smjeten na adresama 0x100, 0x101, 0x102 i 0x103.
Sa ove strane pokazivai su jako fleksibilni, jer pok + 1 e pokazivati na sljedei elemenat
niza (tj. na adresu 0x104) a ne na adresu 0x101. To pokaziva zna jer smo mu specificirali
tip (stavili smo da je pok pokaziva na tip int) i on e, kada ga uveamo, pokazivati na
sljedei elemenat niza. Gotovo nikada poveanje pokazivaa za 1 ne znai i poveanje adrese
za jedan, nego za onoliko bajta koliko zauzima tip pokazivaa.
Zbog toga je zabranjeno vriti dodjelu pokazivaa jednog tipa pokazivau drugog tipa, tj.
ukoliko imamo kd poput sljedeeg:
int x = 10;
double y = 20.4;
int *pint = &x;
double *pdouble = &y;
pint = pdouble; /* OVO NE SMIJEMO RADITI! */

Naravno , ovo treba razlikovati od dodjele *pint = *pdouble koja je potpuno legalna isto
kao i dodjela x = y.

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji
Sada da se vratimo na na niz. Rekli smo da e pok + 1 pokazivati na sljedei elemenat niza,
to znai da se sa jednim pokazivaom moemo kretati kroz niz. Dakle, pok + 2 e
pokazivati na elemenaz niza niz[2], pok + n na elemenat niz[n]. Ako pok + 2
pokazuje na niz[2], onda je *(pok + 2) isto to i niz[2]. Stoga su sljedee tri
konstrukcije jednake:
*(pok + 2) = 10;
/* Isto kao i: */
niz[2] = 10;
/* Isto kao i: */
*(niz + 2) = 10;

Zapravo, indeksiranje niza (niz[i]) nije nita drugo nego pisanje *(niz + i). Poto je
sabiranje komutativno, *(niz + i) je isto to i *(i + niz), pa onda moemo pisati jako
udnu konstrukciju koja e raditi, ali je neitljiva i treba je izbjegavati: i[niz], konkretno za
pristup 4. elementu to je 3[niz]. Jedan mali primjer:
#include <stdio.h>
int main ()
{
int niz[] = {1, 5, 7};
printf ("2[niz] = %d", 2[niz]);
return 0;
}

Primjer iznad, nakon kompajliranja i pokretanja, e ispisati 2[niz] = 7.


Sa pokazivaima, kao specijalnim tipovima u jeziku C, dozvoljen je ogranien skup
aritmetikih operacija (i one ine pokazivaku aritmetiku, tj. aritmetiku koja je dozvoljena
nad pokazivaima na nizove) i to:

Dodavanje cijelog broja pokazivau ne mijenjajui ga: pok + broj


Rezultat je privremena varijabla koja pokazuje na pok + broj adresu
Primjer: int *p = pok + 10
Oduzimanje cijelog broja od pokazivaa ne mijenjajui ga: pok broj
Rezultat je privremena varijavla koja pokazuje na pok - broj adresu
Primjer: int *p2 = p - 3
Uveavanje pokazivaa za cijeli broj, mijenjajui ga: pok += broj ili
pok = pok + broj. Primjer: pok += 2
Umanjivanje pokazivaa za cijeli broj, mijenjajui ga: pok -= broj ili
pok = pok + broj. Primjer: pok -= 4
Prefiksno i postfiksno inkrementiranje pokazivaa: pok++ i ++pok
Rezultat je pokaziva uvean za jednu memorijsku lokaciju.
Prefiksno i postfiksno dekrementiranje pokazivaa: pok-- i --pok
Rezultat je pokaziva umanjen za jednu memorijsku lokaciju.
Oduzimanje dva pokazivaa: pok2 pok1
9

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji
Rezultat nije pokaziva nego cijeli broj koji predstavlja broj memorijskih lokacija
izmeu ta dva pokazivaa. Moe sluiti da se odredi broj elemenata niza koji je
omeen pokazivaima.
Primjer: int broj = pok2 pok1
Iz ovoga slijedi da nije dozvoljeno sabirati pokazivae (a jeste oduzimati ih) kao i to da nije
dozvoljeno mnoiti niti dijeliti pokazivae. Napomenimo da se indeksiranje pokazivaa
pok[i] kao i sinonim indeksiranja *(pok + i) ne smatra pokazivakom aritmetikom.
Spomenimo sada i injenicu da ime niza, upotrijebljeno samo za sebe, predstavlja pokaziva
na prvi elemenat toga niza (ali takav pokaziva ne moemo mijenjati, npr. niz++, to e biti
objanjeno kasnije). Tako da su sljedee dvije linije ekvivalentne:
int *pok1 = &niz[0];
int *pok2 = niz;

Sada ove pokazivae moemo indeksirati, kao i obine nizove: pok2[1] = 100.

Kada smo deklarirali funkcije koje prihvataju nizove kao parametre, pisali smo neto poput
sljedeeg:
void Funkcija (int niz[], int vel);

Pri tome smo spomenuli da nismo slali kopiju niza funkciji, nego se svaka izmjena niza
unutar funkcije odraavala i na originalni niz. Sada moemo detaljnije objasniti zato se to
deavalo. Mi smo funkciji zapravo prosljeivali pokaziva na prvi elemenat niza te smo ga
indeksirali (vidjeli smo da je i to mogue) i direktno mijenjali njegov sadraj. Moda malo
zvui udno pokaziva na prvi elemenat niza a mi proslijedili int niz[], ali, to dvoje je
isto u parametrima funkcije. Iznad napisani prototip je potpuno ekvivalentan sljedeem:
void Funkcija (int *niz, int vel);

Sada stvari postaju jasnije, sada nam je jasno zato smo kao argument funkcije prilikom
njenog pozivanja prosljeivali samo ime niza, tj. pisali smo sljedee:
Funkcija (niz, 7);

Odnosno sljedee (znamo da je ime niza upotrijebljeno samo za sebe zapravo pokaziva na
prvi elemenat toga niza):
Funkcija (&niz[0], 7);

Primjer 2: Bez indeksiranja niza uglastim zagradama ispisati ga pomou funkcije.


#include <stdio.h>
void Ispisi (int *niz, int vel)
{

10

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji
int i = 0;
for (; i < vel; ++i)
printf ("%i ", *(niz + i));
}
int main ()
{
int niz[] = {1, 2, -3, 4, 8, 9};
Ispisi(niz, 6); /* Isto kao: Ispisi (&niz[0], 6); */
return 0;
}

Primjer 3: Napisati funkciju Ispisi koja prima dva pokazivaa na tip int. Prvi pokaziva
predstavlja poetak bloka niza kojeg treba ispisivati, a drugi pokaziva pokazuje na kraj toga
bloka. Funkciju testirati tako to ete u kdu deklarirati niz od 16 elemenata te pozivom
funkcije:
1. Ispisati prvu njegovu polovinu
2. Ispisati zadnjih 5 elemenata toga niza

3. Ispisati drugu njegovu polovinu


4. Ispisati elemente od 5. elementa do 11.
5. Ispisati cijeli niz

Pri tome, koristiti razliite naine prosljeivanja argumenata funkciji.


#include <stdio.h>
void Ispisi (int *poc, int *kraj)
{
while (poc <= kraj) /* (1) */
{
printf ("%i, ", *poc);
poc++; /* Prelazimo na sljedeci elemenat */
}
}
int main ()
{
int niz[] = {1, 2, -3, 4, 8, 9, 7, 8, 0, 18, 2, 37, 11, 7, 9, 7};
printf ("1) ");
Ispisi(&niz[0], &niz[7]); /* (2) */
printf ("\n2) ");
Ispisi(niz + 11, niz + 15); /* (3) */
printf ("\n3) ");
Ispisi(&niz[8], niz + 15);
printf ("\n4) ");
Ispisi(&niz[6], &niz[12]);
printf ("\n5) ");
Ispisi(niz, niz + 15); /* Ili: Ispisi(niz, &niz[15]); */
return 0;
}

Vano je da analiziramo neke dijelove ovoga kda. Funkcija Ispisi prihvata dva pokazivaa
koji e predstavljati pokazivae na poetak i kraj nekog bloka niza. Unutar funkcije, u liniji
oznaenoj sa (1), imamo uslov while petlje poc <= kraj. Ovo ne znai da je ono na to
pokazuje poc manje ili jednako od onoga na to pokazuje kraj. Navedeno moemo itati
dokle god je adresa koju sadri poc manja ili jednaka adresi koju sadri kraj. Uslov e biti
taan dokle god je prvi pokaziva manji ili jednak drugom pokazivau (pok pokaziva
inkrementiramo unutar petlje). Znamo da su adrese u memoriji kontinualne i da se elementi
11

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji
niza nalaze jedan za drugim, zbog toga potpuno validno smo mogli pisati uslov iz isjeka
iznad. Uslov petlje nam je otkrio jo jedan set operacija koje moemo raditi nad
pokazivaima, a to je njihovo poreenje. Sve pokazivae moemo porediti svim relacionim
operatorima (<, >, <=, >=, ==, !=). Vano je spomenuti da pokazivaka aritmetika, kao i
poreenje pokazivaa, jedino ima smisla ukoliko pokazivai pokazuju na elemente niza.
Nema smisla postaviti pokaziva na neku obinu varijablu i onda taj pokaziva inkrementirati
(to e se kompajlirati i raditi, ali po standardu to je nedefinisano, pa ga treba obavezno
izbjegavati). Analizirajmo malo ovo to smo napisali.
int var = 10;
int *pok = &var;
pok++; /* 'pok' je sada nevalidan! na ta on pokazuje? */

Vjerovatno bi pokazivao na sljedeu varijablu (ukoliko takva postoji), npr.


int var1 = 10, var2 = 50;
int *pok = &var1;
pok++; /* 'pok' nije validan, ali sada on vjerovatno pokazuje na 'var2' */

Iz primjera iznad moemo vidjeti da su varijable u memoriji smjetene jedna iza druge.
Varijabla pok bi mogla da pokazuje na varijablu var2, ali to nije garantovano i ne smijemo se
na to osloniti!
Takoer pokaimo jedan primjer viseeg pokazivaa koji e pokazivati na varijablu koja je
umrla, tj. prestala da postoji. Inae, primjer viseih pokazivaa emo objasniti kasnije.
int *pok;
{
int var = 10; /* Lokalna varijabla! */
pok = &var; /* U pok smjestamo adresu lokalne varijable... */
} /* <--- ovdje varijabla 'var' umire! */
/* Ovdje je pokazivac 'pok' viseci, tj. NIJE VALIDAN! */
int nesto = 1;
/* pok vjerovatno pokazuje na 'nesto' varijablu ali to nije garantovano! */

Zbog toga trebamo biti jako paljivi prilikom koritenja pokazivaa. Jo eventualnih problema
zbog neopreznog koritenja pokazivaa emo vidjeti kasnije.
U rjeenju iznad smo mogli primijetiti razliite naine prosljeivanja argumenata funkciji
Ispisi, pa emo sada rei neto i o njima. Poziv sa oznakom (2) je klasian, prosljeujemo
adresu prvog elementa i adresu 8. elementa (to je prva polovina niza). Oznaka (3) nam otkriva
jedan novi nain pozivanja, koji je u biti isti kao i prethodni. Kao to smo pisali pok + i,
ovdje piemo niz + i, prosljeujui pokaziva na i-ti elemenat.
Operator referenciranja, odnosno adresni operator (&) i operator dereferenciranja (*) su
meusobno suprotni, pa ukoliko imamo *&broj, dobivamo samo broj.
Nakon kompajliranja i pokretanja kda iznad, dobivamo sljedee:
12

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji

Primjer 4: Napisati funkciju Ispisi koja prihvata pokaziva na prvi elemenat niza cjelobrojnih
elemenata i koja treba ispisati elemente toga niza unutar vitiastih zagrada, npr. {1, 2, 3}.
Zabranjeno je indeksiranje proslijeenog niza odnosno pokazivaa naredbama niz[i]
odnosno *(niz + i). Funkciju napisati nakon main funkcije, a njen prototip neka bude
lokalan za main funkciju.
#include <stdio.h>
int main ()
{
int niz[] = {1, 2, 3, 4, 5};
void Ispisi (int *, int); /* LOKALNI PROTOTIP */
Ispisi(niz, 5);
return 0;
}
void Ispisi (int *niz, int vel)
{
printf ("{");
int a = 0;
for (; a < vel - 1; ++a)
printf ("%i, ", *niz++); /* (1) */
if (vel > 0)
printf ("%i", *niz);
printf ("}");
}

Poto nam je bilo zabranjeno koristiti indeksiranje niza, morali smo koristiti pokazivaku
aritmetiku i dereferenciranje hodajueg pokazivaa. Ono to je zanimljivo jeste linija kda
koju smo oznaili sa (1), gdje postoji naredba *niz++. Naime, vidjeli smo (u dokumentu
Operatori u jeziku C, dio 1) da postfiksni operator inkrementiranja (p++) ima vei prioritet
od operatora dereferenciranja (*p), pa se taj izraz tretirao kao *(niz++). Kako znamo da
naredba x = y++ znai isto to sljedee dvije naredbe x = y; y++; tako je navedena
naredba znaila *niz; niz++, odnosno ispisivali smo ono na to niz pokazuje te nakon toga
inkrementirali pokaziva. Postoje jo tri naredbe koje kombiniraju operator dereferenciranja i
postfiksne odnosno prefiksne operatore inkrementiranja i dekremeniranja. Preostale
konstrukcije su (*p)++ (uveava postfiksno ono na ta p pokazuje), ++*p (uveava prefiksno
ono na ta p pokazuje) i *++p (dereferencira ve uveani pokaziva p). Naravno, isto vrijedi i
za operator dekrementiranja - npr. --*p itd. Njihovo znaenje je najlake odrediti ako
pogledamo prioritet i asocijativnost operatora. Vie o ovome e biti reeno u drugom dijelu
spomenutog dokumenta, tj. dokumenta Operatori u jeziku C (dio 2).
13

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji
Kada smo spominjali naredbu *niz++ rekli smo da ona vrati ono na ta niz pokazuje i da
postfiksno uvea vrijednost varijable niz. Ovo je imalo smisla jer smo mi koristili ono na ta
niz pokazuje, tj. ispisivali smo ga tako to smo ga prosljeivali printf funkciji. Studenti esto
prave greku i piu *pok++; samostalno, bez da rezultat toga koriste dalje. To nije greka
ali jeste loa praksa, i moe nekad krahirati program (pok++ nee krahirati program ak i
kada je pok nevalidan, ali dereferencirnje nevalidnog pokazivaa *pok je opasno i moe
dovesti do krahiranja programa). Konkretno, ovo se (pogreno) pie (ili neto slino ovome):
while (pok <= kraj);
*pok++; /* Treba samo pok++, bez dereferenciranja! */

Kako su pokazivai zapravo tipovi u jeziku C, to ih funkcija moe i vraati tj. povratni tip
funkcije moe biti i pokaziva. Meutim, postoje odreene opasnosti kada funkcija vraa
pokaziva, na to emo ukazati kasnije, nakon primjera.
Primjer 5: Napisati funkciju Pomnozi koja kao parametre ima niz i broj. Parametar niz
predstavlja pokaziva na prvi elemenat niza cjelobrojnih elemenata, dok parametar broj
predstavlja broj sa kojim treba pomnoiti svaki elemenat niza. Iskoristiti napisanu Ispisi
funkciju za ispisivanje niza. Omoguiti ulanano povezivanje funkcija, tako da je mogue
pisati ovo: Ispisi (Pomnozi (niz, 10, 3), 10);
1. #include <stdio.h>
2. /* Funkcija vraca pokazivac na int */
3. int *Pomnozi (int *niz, int vel, int broj)
4.
{
5.
int *pocetak = niz, a; /* (1) */
6.
for (a = 0; a < vel; ++a, ++niz) /* (2) */
7.
*niz *= broj; /* Isto kao: *niz = *niz * broj; */
8.
return pocetak; /* (3) */
9.
}
10. int main ()
11.
{
12.
int niz[] = {1, 2, 3, 4, 5};
13.
void Ispisi (int *, int); /* LOKALNI PROTOTIP */
14.
Ispisi(Pomnozi (niz, 5, 100), 5); /* (4) */
15.
return 0;
16.
}

Analizirajmo iznad napisano rjeenje. Funkcija Ispisi ovdje nije prikazana radi utede
prostora, dok funkcija Pomnozi prihvata pokaziva na prvi elemenat niza, njegovu veliinu i
broj sa kojim treba pomnoiti svaki elemenat niza. Ona vraa pokaziva kako bismo mogli
ulanano pozivati funkcije (poziv kakav je napisan u 14. liniji gdje smo funkciji Ispisi
proslijedili kao argument rezultat funkcije Pomnozi). U liniji 5, oznaenoj sa (1), smo
deklarirali pokaziva pocetak i inicijalizirali ga sa vrijednosti koju je sadravao pokaziva niz.
Na ovaj nain smo u varijabli pocetak zapamtili adresu prvog elementa niza. Razlog za ovo je
bio to smo u nastavku funkcije mijenjali pokaziva niz (u zaglavlju for petlje) a funkcija je
trebala da vrati pokaziva na poetak niza. Isti efekat smo mogli postii da smo deklarirali
pomonu varijablu (npr. sa imenom pomocna) koju bismo koristili za hodanje kroz niz (npr.
14

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji
sa pomocna++) i u tome sluaju bismo vratili vrijednost pokazivaca niz (uz pomo naredbe
return niz;) jer je u tijelu funkcije nismo mijenjali.
Oznaka (2), linija 6, je zanimljiva po tome to smo u zaglavlju for petlje inkremenriali dvije
varijable (naredbama ++a i ++niz). Ovo smo uradili da se podsjetimo da je i to dozvoljeno.
Isti efekat bismo dobili da smo poveavali pokaziva niz na kraju bloka for petlje, tj. da smo
pisali ++niz nakon naredbe *niz *= broj. Naravno, tada bismo taj kd morali smjestiti
unutar vitiastih zagrada! Mogli smo ovo i krae napisati, tako da poveanje pokazivaa
uradimo u bloku for petlje, na sljedei nain *niz++ *= broj.
Sa linijom 8, oznaenom sa (3), mi smo vratili zapameni pokaziva na poetak niza.
Funkcija je trebala vratiti adresu prvog elementa niza upravo da bismo istu proslijedili Ispisi
funkciji, jer ona ispisivanje treba da pone polazei od prvog elementa.
Na kraju, u liniji 14, smo pozvali obje funkcije, lanano, prosljeujui rezultat Pomnozi
funkcije funkciji Ispisi. Ukoliko kompajliramo kd iznad i pokrenemo, bie ispisano sljedee:
{100, 200, 300, 400, 500}
Razmotrimo sada opasnost vraanja pokazivaa iz funkcije. U primjeru iznad pokaziva kojeg
smo vratili iz funkcije je zapravo pokaziva kojeg smo funkciji i proslijedili. Taj pokaziva je
pokazivao na poetak niza, isto kao i pokaziva kojeg smo vratili. Znai, vraeni pokaziva je
pokazivao na neki ve postojei objekat (konkretno, varijablu niz deklariranu u main funkciji)
i takav pokaziva je validan. Razmotrimo primjer da, unutar funkcije, deklariramo (lokalnu)
varijablu. Ukoliko sada vratimo pokaziva koji sadri adresu te varijable, taj pokaziva e biti
visei (engl. dangling). Zato? Zato to e lokalna varijabla umrijeti (prestati da postoji)
kada se funkcija zavri i na vraeni pokaziva e pokazivati na nepostojeu varijablu, tj.
pokazivati e na adresu gdje je nekad prije ivjela varijabla. Dereferenciranje viseeg
predstavlja greku i esto e dovesti do krahiranja programa. Pogledajmo na konkretnom
sluaju iznad opisano:
int *Funkcija ()
{
int x = 10; /* Lokalna varijabla */
int *pok = &x;
return pok; /* Vracamo viseci pokazivac */
/* Ovdje varijabla 'x' umire! */
}
int main ()
{
int *viseci_pok = Funkcija();
/* Dereferenciramo viseci pokazivac! */
*viseci_pok = 10; /* STA SE OVDJE DESAVA? */
return 0;
}

15

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji
Mogli smo direktno vratiti adresu varijable x (naredbom return &x;), tj. uopte nije bilo
potrebe da deklariramo varijablu pok unutar funkcije. Tada bi funkcija izgledala ovako:
int *Funkcija ()
{
int x = 10; /* Lokalna varijabla */
return &x; /* Vracamo adresu od varijable koja ce umrijeti ubrzo */
}

Ukoliko sada pokuamo kompajlirati program, dobiti emo sljedee upozorenje (koje u
velikoj veini sluajeva predstavlja greku): C:\...main.c|6|warning: function returns
address of local variable [enabled by default]|. Ovdje je i kompajler bio u mogunosti
prepoznati vjerovatnu greku vraanja adrese lokalne varijable.
Sada da objasnimo jo jednu, naizgled udnu, konstrukciju prilikom indeksiranja pokazivaa
na neki elemenat niza: pok[-1]. Ovo moemo uraditi kada pokaziva pok pokazuje na neki
elemenat niza koji nije prvi. Npr. ako imamo sljedee:
int niz[] = {1, 2, 3, 4, 5, 6, 7};
/* 'pok' pokazuje na 4. elemenat niza, tj. broj 4 */
int *pok = niz + 3; /* Isto kao: int *pok = &niz[3] */
pok[-1] = 10; /* pok[-1] je isto to i niz[2] */
pok[-3] = 100; /* pok[-3] je isto to i niz[0] */
*(pok - 2) = 100; /* *(pok - 2) je isto to i niz[1] */

Naravno, ovo ima smisla jedino ukoliko pok[-indeks] ne izlazi izvan opsega niza.
Vidjeli smo takoer da ako deklariramo pokaziva koji pokazuje na poetak niza, taj
pokaziva kasnije moemo mijenjati:
int niz[] = {1, 2, 3, 4, 5};
int *pok = niz;
/* Mozemo pisati ovo: */
pok++;
/* Ali NE MOZEMO ovo: */
niz++;

Kao to vidimo, niz++ naredba e javiti greku. Ime niza, upotrijebljeno samo za sebe,
predstavlja pokaziva na prvi elemenat toga niza. Ali to nije obini, nego konstantni
pokaziva (tj. pokaziva iju vrijednost ne moemo mijenjati, ali moemo mijenjati vrijednost
onoga na to on pokazuje). Ako bolje razmislimo shvatiemo da je tako moralo biti, jer
ukoliko bismo imali mogunost pisanja naredbe niz++ (ili bilo kojeg drugog izraza koji bi
promijenio vrijednost pokazivaa niz) izgubili bismo trajno adresu poetka niza, to je
naravno nedopustivo.
U prethodnom paragrafu smo spomenuli pojam konstantnog pokazivaa. To je pokaziva iju
vrijednost ne moemo mijenjati (ali i dalje moemo mijenjati vrijednost onoga na to on
pokazuje). Takvi pokazivai mogu imati primjene gdje mi ne elimo da moemo mijenjati
njihove vrijednosti. Deklaracija takvog pokazivaa bi bila sljedea:
16

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji
int * const pok;

Iznad deklarisani pokaziva se naziva konstantni pokaziva na tip int.


Moemo deklarirati i obini pokazivai na neku konstantnu varijablu, tj. da pokaziva
moemo mijenjati ali da ono na to on pokazuje ipak ne moemo mijenjati. Takvi pokazivai
imaju veliku primjenu u jeziku C kod prosljeivanja stringova funkcijama. Moemo, takoer,
deklarirati i konstantni pokaziva na konstantnu varijablu. Vie o ovome e biti govora u
dokumentu Operatori u jeziku C (dio 2).
Objasnimo i ta to predstavlja nul-pokaziva. To je pokaziva koji ima vrijednost 0. On,
teoretski, pokazuje na objekat koji se nalazi adresi 0. Poto takva adresa ne postoji, takav
pokaziva nee pokazivati ni na koji objekat.
int *pok = 0; /* nul-pokazivac */

Ipak, zbog nekih tehnikih razloga, za nul-pokaziva je uvedena i makro konstanta NULL
koja ima vrijednost nula. Uz pomo spomenute makro konstante pisali bismo sljedee:
int *pok = NULL;

Konstanta NULL je vjerovatno definisana na sljedei nain: #define NULL (void *)0 i
njena definicija se moe nai u nekoliko standardnih biblioteka (ukljuujui i biblioteku
<stdlib.h>).
U paragrafu iznad smo mogli vidjeti dvije stvari, void pokazivae tj. pokazivae tipa void * i
pretvorbu odnosno konverziju (engl. cast) pokazivaa. Ovo emo objasniti pred kraj ovoga
dokumenta.
Spomenimo sada viestruke pokazivae. Ako pokaziva pokazuje na pokaziva, takav
pokaziva se naziva dvostruki pokaziva. Na isti nain moemo definisati i viestruke
pokazivae. Ako pokaziva pokazuje na pokaziva, da bismo izmijenili ono na to on
pokazuje (tj. pokaziva), potrebno je izvriti jednostruko dereferenciranje (npr. *pok).
Ukoliko pak elimo uz pomo dvostrukog pokazivaa izmijeniti ono na to pokazuje
pokaziva na kojeg on pokazuje, tada nam je potrebno dvostruko dereferenciranje (npr.
**pok). Pokaimo na primjeru:
int broj = 10;
int *p = &broj; /* Jednostruki pokazivac */
int **pp = &pbroj; /* Dvostruki pokazivac */
*p = 20; /* Postavlja vrijednost varijable 'broj' na 20 */
**pp = 50; /* Postavlja vrijednost varijable *p tj. 'broj' na 50 */
/*
*pp = 100;
Ovo bi postavilo vrijednost pokazivaca p na 100, tj. on vise ne bi
pokazivao na varijablu 'broj' nego na nesto sto se nalazi na adresi 100.
Ima isti efekat kao p = 100.
*/

17

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji
Kao to znamo, niz[i] je isto to i *(niz + i). ta se deava sa 2D nizovima? Kako se
oni dereferenciraju? Pa analogno 1D nizovima. Ako imamo 2D niz (npr. int mat[2][2])
onda je mat[i] zapravo i-ti red matrice. To je zapravo isto to i *(mat + i). Ako elimo
pristupiti elementu matrice pristupiemo sa mat[i][j]. To je isto to i (*(mat + i))[j]
a to je isto (slijedei analogiju) kao i ovo: *(*(mat + i) + j).
Primjer 6: Napisati funkciju IspisiMatricu koja ispisuje proslijeenu matricu formata 44.
Funkcija ne smije koristiti indeksiranje uglastim zagradama.
Znamo da trebamo ispisati matricu formata 44. Ve smo spominjali da e funkcija morati da
zna koliko kolona ta matrica ima (zbog interne reprezentacije viedimenzionalnih nizova u
memoriji koji su predstavljeni kao jednodimenzionalni nizovi).
#include <stdio.h>
void IspisiMatricu (int mat[][4], int n, int m)
{
int i, j;
for (i = 0; i < n; ++i)
{
for (j = 0; j < m; ++j)
printf ("%3i", *(*(mat + i) + j));
printf ("\n");
}
}
int main ()
{
int matrica[4][4] = {1, 2, 3, 4, 5, 6, 7};
IspisiMatricu (matrica, 4, 4);
return 0;
}

Vidjeli smo nain pristupanja elementima matrice preko sljedee naredbe koja se nalazi u
zaokruenoj liniji *(*(mat + i) + j)). Podsjetimo se jo da e ostali elementi matrice
(oni koje nismo naveli prilikom inicijalizacije) biti nule.
Primjer 7: Prepraviti prethodni primjer tako da ispisuje elemente matrice, ali iskljuivo
koritenjem pokazivake aritmetike, dakle nije dozvoljeno indeksiranje niti uglastim
zagradama niti njegovim sinonimom *(pok + i).
#include <stdio.h>
void IspisiMatricu (int (*mat)[4], int n, int m) /* (1) */
{
int i, j;
for (i = 0; i < n; ++i)
{
int *pok = *mat; /* (2) */
for (j = 0; j < m; ++j)
printf ("%3i", *pok++);
printf ("\n");
mat++; /* Prelazimo u sljedeci red matrice (3) */
}

18

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji
}
int main ()
{
int matrica[4][4] = {1, 2, 3, 4, 5, 6, 7};
int (*pok_mat)[4] = matrica; /* Isto kao: = &matrica[0]; */
IspisiMatricu (pok_mat, 4, 4); /* (4) */
return 0;
}

Analizirajmo ovo rjeenje. Stavili smo da funkcija prihvata pokaziva na niz kao cjelinu. Isti
efekat bismo postigli da smo stavili kao i u prethodnom rjeenju, tj. (int mat[][4]), ali
ovaj put smo stavili ovo drugo da znamo ta ono u funkcijskom parametru predstavlja. Isto
kao to znamo da smo funkciji za niz prosljeivali obini pokaziva (int *pok), znai
prosljeivali smo adresu prvog elementa niza, tako i za matricu prosljeujemo adresu prvog
njenog elementa a to je cijeli niz! Mi njoj trebamo proslijediti adresu cijelog niza odnosno
niza kao cjeline. Poto nismo smjeli koristiti nikakvo indeksiranje, ispisivanje smo radili samo
sa pokazivaima. U liniji sa oznakom (2) smo deklarirali obini pokaziva koji e pokazivati
na prvi elemenat reda matrice, inicijalizirali smo ga vrijednosti sa *mat. Ispisivali smo
vrijednost *pok i odmah ga postfiskno inkrementirali, kao i u jednom od ranijih primjera. Na
kraju smo poveavali pokaziva na niz kao cjelinu sa mat++. Isto kao to pok++ prelazi na
sljedei elemenat niza, tako i mat++ prelazi na sljedei elemenat matrice, tj. prelazi na novi
red. Prilikom pozivanja funkcije mogli smo doslovno proslijediti matrica varijablu, ali smo
namjerno deklarirali pokaziva na niz kao cjelinu da vidimo kako se i to radi. Morali smo
staviti ime pokazivaa unutar zagrada jer bi prosta konstrukcija int *pok[4] deklarirala niz
pokazivaa, tj. niz iji su elementi pokazivai na int. Funkciji nismo mogli proslijediti ni
dvostruki pokaziva, tj. int **mat, jer smo vidjeli da je prvi elemenat matrice niz kao
cjelina pa smo trebali na njega da proslijedimo pokaziva.

Za one koji ele znati vie slijedi nekoliko reenica o void pokazivaima te dinamikoj
alokaciji memorije u jeziku C; svakako se preporuuje da svi ovo barem proitaju a oni
zainteresirani i detaljno analiziraju.
Objasnimo void pokazivae (spomenute prilikom opisivanja naina na koji je vjerovatno
definisana makro naredba NULL). U prethodnoj analizi smo spomenuli da varijablu ne
moemo deklarirati da bude tipa void ali smo takoer spomenuli da je ipak mogue deklarirati
varijablu koja je pokaziva na tip void. Takav pokaziva se naziva generikim pokazivaom, i
on ima sljedee znaenje: ne zna se koji je tip na koji on pokazuje, ali moe pokazivati na bilo
koji tip. Takav pokaziva, tj. pokaziva tipa void * se ne moe dereferencirati, ne moe se nad
njim vriti nikakva aritmetika i on je neupotrebljiv sve dok se nad njim ne izvri konverzija
(koju emo opisati u nastavku).
Konverzija (engl. cast) je pretvaranje jednog tipa u drugi tip. Ve smo se susretali sa
konverzijama obinih tipova npr. (double)brojac i sl. Konverzija izmeu pokazivaa je
slina, pretvaramo pokaziva jednog tipa u pokaziva drugog tipa. ak pretvorba ne mora biti
19

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji
izmeu dva pokazivaa; moe biti npr. izmeu varijable tipa int i pokazivaa (prikazano iznad
u definiciji NULL makro naredbe). Ono to je vano napomenuti da konverziju pokazivaa
treba izbjegavati kada god se to moe! Ovdje je spomenuta samo da bismo nali upotrebu
void pokazivaa, te emo to i prikazati. Maloprije smo opisali funkciju koja je ispisivali niz
iji su elementi cijeli brojevi (tj. tipa int). Napiimo sada tu funkciju sa generikim
pokazivaima (pretpostavlja se da je ukljuena biblioteka <stdio.h>):
void Ispisi (void *niz, int vel)
{
int a = 0;
for (; a < vel; ++a)
printf ("%i, ", ((int *)niz)[a]);
}
int main ()
{
int niz[] = {1, 2, 3, 4, 5};
Ispisi(niz, 5); return 0;
}

Ono to se moe primijetiti jeste da smo vrili (neophodnu) konverziju pokazivaa. Iz prvog
pokazivaa (tipa void *) u pokaziva (tipa int *), jer nismo mogli samo napisati niz[a] jer
ne moemo dereferencirati pokaziva koji je tipa void, a znamo da je niz[a] isto to i
sljedea konstrukcija *(niz + a) koja predstavlja dereferenciranje.

Konano, spomenimo nakratko i dinamiku alokaciju memorije u jeziku C. Znamo da smo


imali odreena ogranienja prilikom deklariranja obinih (statikih) nizova. Ako smo od
korisnika traili da unese broj elemenata n, mi nismo smjeli deklarirati niz kojem je veliina
varijabla n, tj. nismo smjeli pisati ovo: int niz[n]. Razloge smo objasnili u analizi koja je
obuhvatila nizove. Rjeenje je bilo napraviti dovoljno veliki niz (npr. od 1000 elemenata),
koji je u veini sluajeva bio neiskoriten. Sada emo predstaviti univerzalnije rjeenje:
pravimo dinamiki niz od onoliko elemenata koliko nam je potrebno. Proces zauzimanja
memorije naknadno (nakon to je program startao) naziva se dinamika alokacija memorije.
Poto je ona kompleksna tema, mi ovdje neemo ulaziti u detaljniju analizu iste. Samo emo
spomenuti da dinamika alokacija moe da ne uspije, tj. da ne bude slobodne memorije (ili iz
nekog drugog razloga to emo vidjeti ispod), to svakako moramo provjeriti. Takoer,
zauzetu memoriju mi moramo osloboditi jer e u suprotnom doi do njenog curenja (engl.
memory leak). Sve su to stvari na koje treba obratiti panju a kojih emo se mi samo povrno
dotaknuti.
Da bismo dinamiki alocirali niz koristiti emo malloc funkciju iz <stdlib.h> biblioteke.
Postoje jo neke funkcije koje vre alokaciju memorije, ali mi njih neemo ovdje koristiti. Za
oslobaanje memorije emo koristiti free funkciju iz iste biblioteke. Napomenimo da malloc
funkcija radi sa bajtima memorije (kako bi se mogla alocirati memorija za razliite tipove), te
zbog toga ona vraa pokaziva tipa void na poetak alocirane memorije. Mi emo morati

20

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji
uraditi konverziju toga pokazivaa u odgovarajui tip. Takoer, ako alokacija ne uspije
malloc funkcija e vratiti nul-pokaziva.
#include <stdio.h>
#include <stdlib.h>
int *AlocirajNiz (int broj_el)
{
int *pok = (int *)malloc (sizeof (int) * broj_el);
return pok;
}
void Oslobodi (int *niz)
{
free (niz);
}
int main ()
{
int n = 0;
printf ("Unesite broj elemenata niza: ");
scanf ("%d", &n);
int *niz = AlocirajNiz (n);
if (niz == NULL)
{
printf ("Alokacija nije uspjela!\n");
return -1;
}
printf ("Unesite elemente niza:\n");
int a = 0;
for (; a < n; ++a)
{
printf ("niz[%i] = ", a);
scanf ("%i", &niz[a]);
}
printf ("Uneseni niz naopako ispisan: \n");
for (a = n - 1; a >= 0; --a)
printf ("%i, ", niz[a]);
/* Moramo osloboditi zauzetu memoriju: */
Oslobodi (niz);
return 0;
}

Ovdje moemo, u funkciji AlocirajNiz, primijetiti da smo koristili i sizeof operator. Naime, taj
operator vraa veliinu, u bajtima, nekog objekta. Ve smo spomenuli da malloc alocira bajte
pa mi moramo alocirati dovoljno bajta. Ako tip int zauzima 4 bajta, onda moramo alocirati 40
bajta za 10 elemenata tipa int. Poto mi ne znamo (jer to ovisi od implementacije) koliko bajta
zauzima koji tip, koristimo sizeof operator koji e nam to otkriti. Ostalo smo ve objasnili u
uvodu. Vidjeli smo da funkcija AlocirajNiz nije vratila visei pokaziva nego pokaziva na
alocirani dio memorije (koji je potpuno validan). Razmislite zato vraeni pokaziva nije
visei.
Pokaimo sada dva primjera unosa razliitih brojeva, za koje e alokacija uspjeti i za koje
nee.

21

Osnove Raunarstva Analiza devetog tutorijala


Akademska 2014/2015
Enil Paji

Ne moemo alocirati niz od -1 elemenata. Ovo smo svakako trebali provjeriti prije nego to i
pozovemo funkciju AlocirajNiz ali smo ovdje namjerno ostavili to da moemo pokazati da e
malloc funkcija vratiti nul-pokaziva u sluaju neuspjene alokacije.

Ovdje je zbog nedostatka prostora samo spomenuta dinamika alokacija i povrnu dotaknute
njene prednosti (zaista ih ima veliki broj) kao i njeni problemi prilikom neopreznog
koritenja.
Znai, ukoliko se odluimo da koristimo dinamiku alokaciju u programu, trebamo biti jako
oprezni i paziti na mnogo detalja!

22

Você também pode gostar