Показивачи

Оперативна меморија рачунара организована је као низ локација са придруженим адресама, где је најмања адресабилна јединица један бајт. Подаци у оперативној меморији записују се у узастопним бајтовима. На пример, податак типа int записују се у четири узастопна бајта, податак типа double у осам узастопних бајтова итд.

Показивачка променљива садржи меморијску адресу податка на кога показује (то је увек адреса бајта са најнижом адресом, односно, адреса првог бајта у низу узастопних бајтова).

Иако су меморијске адресе цели бројеви, показивачки типови се у програмском језику C стриктно разликују од целобројног типа. Такође, у програмском језику C постоји више показивачких типова, где се показивачки тип одређује на основу типа податка на који показује.


Показивачке променљиве декларишемо на следећи начин:

tip *identifikator;

где је tip тип податка на који се показује, а identifikator име показивачке променљиве. Приликом декларације није битно да ли постоји размак између звездице и типа или звездице и идентификатора – звездица се увек односи на идентификатор. У следећем примеру:

int* pa;
int *pb;
int * pc;

декларисали смо показивачкe променљивe pa, pb и pc које показују на податкe типа int.


Унарни оператор & (којег називамо адресним оператором, односно оператором референцирања) враћа меморијску адресу свог операнда, стим да операнд не може бити константа нити израз. У следећем примеру:

int a = 3, *pa;
pa = &a;

у меморијски простор целобројне променљиве a уписана је вредност 3, а у меморијски простор показивачке променљиве pa уписана је меморијска адреса променљиве a. За pa кажемо да показује на a. Ово смо могли записати и у једној линији:

int a = 3, *pa = &a;

Поред улоге означавања показивачких променљивих, унарни оператор * (кога називамо оператором дереференцирања) враћа вредност која се налази у меморијском простору на коју показивачка променљива показује, увек водећи рачуна о типу податка који се у њој налази. У следећем примеру:

int a = 3, *pa = &a;
printf("%d", *pa);

у простор променљиве a уписана је вредност 3, у простор променљиве pa уписана је адреса променљиве a и на крају је одштампана вредност на коју показује pa. Шта ће се исписати на излазу? Исписаће се 3 јер је вредност на коју показује pa у ствари вредност променљиве a.


Резимирајмо све до сада. У следећем програму:

#include <stdio.h>
int main(void)
{
	int x = 100, *px = &x;
	printf("x   : %d\n", x);
	printf("&x  : %p\n", &x);
	printf("px  : %p\n", px);
	printf("*px : %d\n", *px);
}

на екрану штампамо следеће:

  • вредност променљиве x
  • адресу променљиве x, тј. &x
  • вредност променљиве px
  • вредност променљиве на коју показује px, тј. *px

По извршењу датог програма добијамо следећи резултат

x   : 100
&x  : 010FF7C0
px  : 010FF7C0
*px : 100

Јасно је да су једнаке вредности променљиве x и променљиве на коју показује px, као и да је адреса променљиве x једнака са вредношћу променљиве px.


Ако се у изразу дереференцираном показивачу додељује вредност, онда измена његове вредности утиче и на простор на који показује (ако показује на променљиву, мења се садржај те променљиве). У следећем примеру:

int a, *pa;
a = 3;
pa = &a;
*pa = 6;

у простор променљиве a уписана је вредност 3, у простор променљиве pa уписана је адреса променљиве a и у простор на који показује pa уписана је вредност 6. Уписом вредности 6 у простор на који показује pa промењена је и вредност променљиве a на 6!


Генерички показивачи

Поред показивача на податке познатих типова (int, float…), постоје и показивачи код којих није одређен тип података на који показују. Такве показиваче називамо генеричким показивачима и декларишемо их помоћу кључне речи void коју наводимо уместо типа података. Помоћу генеричких показивача не може се приступити подацима у меморији без претходне конверзије у познати тип података. У следећем примеру:

    int a;
    void *pa;
    a = 3;
    pa = &a;
    *(int*)pa = 6;
    printf("%d", *(int*)pa);

у простор променљиве a уписана је вредност 3, у простор генеричког показивача pa уписана је адреса променљиве a, а у простор на који показује pa уписана је вредност 6 уз претходну конверзију у познати тип података int. Такође, испис на стандардни излаз вредности променљиве на коју показује pa извршен је уз конверзију у познати тип података.

Да конверзије нису извршене, компајлер би упозорио на дереференцирање генеричког показивача и избацио грешку о неправилном коришћењу кључне речи void.


Придруживање идентификатора показивачким типовима

Наредбом typedef могу се придружити идентификатори и стандардним типовима података и показивачима. У следећем примеру:

    typedef int ceoBroj;
    typedef int *pceoBroj;
    ceoBroj a = 3;
    pceoBroj pa = &a;
    printf("%d %p", a, pa);

дефинисан је  целобројни тип ceoBroj и показивачки тип који показује на целе бројеве pceoBroj. Декларисана је и иницијализована променљива a која је типа ceoBroj. Декларисан је и иницијализован показивач pa који је типа pceoBroj. Одштампана је вредност променљиве a као и њена адреса, односно вредност показивача pa.


Операције са показивачима

Над показивачима је могуће извршити неколико операција. Могућа је додела вредности једног показивача другом помоћу оператора = након чега оба показивача показују на исти податак. У следећем примеру:

    int a = 3, b = 6, *pa = &a, *pb = &b;
    pb = pa;

декларисане су и иницијализоване две целобројне променљиве a и b и два показивача на њих pa и pb. Након тога, вредност показивача pa додељена је вредношћу показивача pb. То значи да сада оба показивача показују на исти целобројни податак a.

Напомена: грешка је додељивати вредност једног показивача другом, ако показивачи не показују на исти тип података. Изузетак важи за генеричке показиваче у случају када се додељује вредност негенеричког показича генеричком. Помоћу генеричког показивача се свакако не може приступити показиваном податку, па и није битно ког је типа тај податак. Обрнуто не важи –  додељивање вредности генеричког показивача не генеричком може довести до грешке.

Може се вршити додела нуле показивачу и поређење показивача са нулом. Вредност нула (NULL) говори да показивач не показује ни на један податак.

Оператори за једнакост == и неједнакост != могу се користити над два показивача истог типа (укључујући и генеричке показиваче) приликом провере да ли они показују на исти податак.

Могуће је поређење два показивача истог типа (осим генеричког) операторима <, <=, > и >=, израчунавање разлике два показивача (осим генеричког) оператором и израчунавање збира и разлике једног показивача (осим генеричког) са податком целобројног типа. Ове операције имају смисла када се изводе над показивачима на елементе низа, а не на независне податке. Ово ће бити демонстрирано у наредним лекцијама, а сада само наведено као чињеница.