Skip to content

Показивачи

Оперативна меморија рачунара организована је као низ локација са придруженим адресама, где је најмања адресабилна јединица један бајт. Подаци у оперативној меморији записују се у узастопним бајтовима. На пример, податак типа int записују се у четири узастопна бајта, податак типа double у осам узастопних бајтова итд. Показивачка променљива садржи меморијску адресу податка на кога показује (то је увек адреса бајта са најнижом адресом, односно, адреса првог бајта у низу узастопних бајтова). Иако су меморијске адресе цели бројеви, показивачки типови се у програмском језику C стриктно разликују од целобројног типа. Такође, у програмском језику C постоји више показивачких типова, где се показивачки тип одређује на основу типа податка на који показује. Показивачке променљиве декларишемо на следећи начин:

tip *identifikator;

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

#include <stdio.h>
main()
{
    int* pa;
    int *pb;
    int * pc;
}

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

#include <stdio.h>
main()
{
    int a = 3, *pa;
    pa = &a;
}

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

#include <stdio.h>
main()
{
    int a = 3, *pa = &a;
}

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

#include <stdio.h>
main()
{
    int a = 3, *pa = &a;
    printf("%d", *pa);
}

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

#include <stdio.h>
main()
{
	int a = 3, *pa = &a;
	printf("%d\t%p\t%p\t%d", a, &a, pa, *pa);
}

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

3       008FFC80        008FFC80        3

Оперативни систем је по извршењу програма променљивој a доделио адресу 008FFC80. Приликом следећег извршења овог истог програма, то ће бити нека друга адреса. Ово важи и за све остале примере у наставку у којима се наводе меморијске адресе. Шта ће се исписати на излазу у следећем примеру?

#include <stdio.h>
main()
{
	int a = 3, *pa = &a;
	*pa = 4;
	printf("%d", a);
}

Исписаће се 4.


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

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

#include <stdio.h>
main()
{
    int a = 3;
    void *pa = &a;
    printf("%d", *(int*)pa);
}

целобројној променљивој a додељена је вредност 3, генеричком показивачу pa додељена је адреса променљиве a. Приликом исписа вредности на коју показује pa, извршена је конверзија у познати тип података int. Да конверзијa ниje извршенa, компајлер би избацио грешке и упозорења. Шта ће се исписати на излазу у следећем примеру?

#include <stdio.h>
main()
{
    int a = 3;
    void *pa = &a;
    *(int*)pa = 4;
    printf("%d", a);
}

Исписаће се 4.


Придруживање идентификатора

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

#include <stdio.h>
main()
{
    typedef int ceoBroj;
    typedef int *pceoBroj;
    ceoBroj a = 3;
    pceoBroj pa = &a;
    printf("%d\t%p", a, pa);
}

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

3       006FFCD8

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

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

#include <stdio.h>
main()
{
    int a = 3, b = 4, *pa = &a, *pb = &b;
    pb = pa;
    printf("%d\t%p\t%d\t%d\t%p\t%d", a, pa, *pa, b, pb, *pb);
}

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

3       010FF740        3       4       010FF740        3

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

#include <stdio.h>
main()
{
    int a = 3, b = 4, *pa = &a, *pb = &b;
    pb = NULL;
    if (pa == NULL)
        printf("pa pokazuje na NULL");
    else
        printf("pa pokazuje na %d\n", *pa);
    if (pb == NULL)
        printf("pb pokazuje na NULL");
    else
        printf("pb pokazuje na %d", *pb);
}

иницијализоване је целобројна променљива a и показивач pa који показује на a, и променљива b и показивач pb који показује на b. Потом је показивачу pb додељена вредност NULL. Испитивање једнакости показивача pa и pb са NULL даје следећи излаз:

pa pokazuje na 3
pb pokazuje na NULL

Шта ће се исписати на излазу након извршења следеће програма:

#include <stdio.h>
main()
{
    int *a = NULL, b = 2;
    a = &b;
    *a = 3;
    b++;
    printf("%d", b);
}

Исписаће се 4.

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

#include <stdio.h>
main()
{
    int a = 3, b = a, *pa = &a, *pb = &b;
    if (pa == pb)
        printf("pa i pb pokazuju na isti podatak");
    else
        printf("pa i pb ne pokazuju na isti podatak");
}

иницијализоване је целобројна променљива a и показивач pa који показује на a, и променљива b и показивач pb који показује на b. Иако је променљивој b додељена вредност променљиве а, pa није једнако са pb јер не показују на исти податак, па ће се на излазу добити:

pa i pb ne pokazuju na isti podatak

Ако упоређујемо вредности на које показују pa и pb:

#include <stdio.h>
main()
{
    int a = 3, b = a, *pa = &a, *pb = &b;
    if (*pa == *pb)
        printf("vrednosti na koje pokazuju pa i pb su jednake");
    else
        printf("vrednosti na koje pokazuju pa i pb nisu jednake");
}

на излазу ћемо добити:

vrednosti na koje pokazuju pa i pb su jednake

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

Задаци за вежбу

Приручник 2022/53. У програмском језику C, декларисане су и иницијализоване променљиве:

int x = 40, y = 50, z = 60, *p1, *p2;

Одреди које ће вредности имати променљиве x, y и z после извршења следећег кода и упиши на одговарајућу линију:

p1 = &x;
p2 = p1;
y = (*p2) + 20;
z = *p2;

x = 40; y = 60; z = 40;