Оперативна меморија рачунара организована је као низ локација са придруженим адресама, где је најмања адресабилна јединица један бајт. Подаци у оперативној меморији записују се у узастопним бајтовима. На пример, податак типа 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;