Динамичка алокација меморије¶
До сада си у својим програмима најчешће користио тзв. статичке променљиве. Зашто статичке? Зато што се на почетку програма резервише потребан меморијски простор, а он остаје резервисан и заузет до краја извршења програма.
int x, a[100];
char ime[25], c, *p
су само неки познати примери декларисања статичких променљивих.
Посматрај једноставан пример. Желимо да нађемо збир два броја a
и b
, ту
вредност доделимо трећој променљивој с
и прикажемо тај резултат.
#include<stdio.h>
int main(void)
{
int a, b, c;
printf("Unesi broj a: ");
scanf("%d", &a);
printf("Unesi broj b: ");
scanf("%d", &b);
c = a + b;
printf("c = %d", c);
return 0;
}
Резултат извршавања програма:
Unesi broj a: 17
Unesi broj b: 24
c = 41
Шта примећујемо? Променљиве a
, b
и c
, које смо декларисали на почетку, заузимају
меморију све док се програм не изврши.
Променљиве a
, b
, c
заузимају меморију током извршавања програма:

Када су програми овако једноставни и када се заузима мала количина меморије, то и није неки проблем. Али ако се у програму користе веће количине података и ако постоји ограничење заузетости меморије, може настати проблем.
Као што смо рекли, идеја је да се меморија ослободи по извршењу неког дела програма и да се по потреби додели неким другим подацима x
и y
.
Ослобађање претходно заузете меморије и додела исте другим променљивим:

Ово је само илустрација проблема – прича о динамичком заузимању меморије је мало сложенија и треба нам предзнање о показивачима.
Па да наставимо.
Статички подаци се смештају у део меморије који се назива статичка зона меморије.
Напоменули смо да је заузимање меморије све време током извршавања програма често нерационално, посебно у задацима где постоји ограничење заузете меморије. Ово је, иначе, уз временско ограничење извршења задатака, захтев такмичарских задатака. Зато се често користи техника да се простор, резервисан у току извршења програма, ослободи и искористити за смештање других променљивих. Ове променљиве се називају динамичке променљиве. Можеш да претпоставиш да се део меморије у који се смештају динамички подаци назива динамичка зона меморије.
Динамички подаци немају имена, већ се њима приступа помоћу показивача, односно адреса које се чувају у показивачима.
Функције за управљање меморијом у C-у су:
malloc()
calloc()
free()
realloc()
Ове функције се налазе у заглављима stdlib.h
i malloc.h
.
Заузимање меморије¶
За креирање динамичких података користе се функције malloc()
и calloc()
, док
се за њихово уништавање користи функција free()
.
Функција malloc
има следећи облик:
void *malloc(int br);
(обрати пажњу на подразумевани тип функције)
Функција резервише простор у меморији величине br
бајтова и као резултат враћа показивач
на почетак резервисаног простора. Ако се тражена количина меморије не може резервисати,
као резултат се добије NULL
показивач, чије значење смо већ објаснили.
На пример, наредбом malloc(100)
резервише се 100 бајтова у меморији.
Функција malloc(100)
резервише 100 бајтова у меморији и почетну адресу смешта у
претходно дефинисан показивач р
:

Да се још једном подсетимо наредбе sizeof()
коју ћемо често користити. Она враћа величину
операнда изражену у бајтовима. На пример, sizeof(int)
ће вратити 4 или 2, у зависности од
компајлера.
Дакле, после наредби…
double x;
int s = sizeof(x);
променљива s
ће добити вредност 8.
Приметио си из дефиниције функције да је резултат функције malloc()
типа void
, што значи
да није дефинисано за који тип податка у њему може бити смештена адреса. Међутим, резултат
функције се може придружити неком показивачу преко cast
оператора.
Анализирајмо речено на следећем примеру:
int *p;
p = (int*)malloc(sizeof(int));
*p = 5;
У првом реду смо дефинисали показивач који треба да добије неку адресу. Овде смо одабрали да то буде адреса целобројног податка.
Изглед оперативне меморије наредбе int *p
би сликовито могао да изгледа овако:

Другом наредбом смо помоћу malloc
функције резервисали простор у оперативној меморији
величине 4 бајта, наравно за смештање целобројног податка. Адресу резервисаног простора
смештамо у креираном показивачу p
.
Изглед меморије после наредбе p = (int*)malloc(sizeof(int))
:

Једноставном наредбом, индиректним адресирањем, сада смештамо неку вредност у резервисани простор (5).
Изглед меморије после наредбе *p = 5
:

Заузимање меморије се може извршити и помоћу функције calloc()
:
void *calloc( int n, int br);
Функција резервише простор у меморији и иницијализује је на вредност 0 за n елемената
величине br бајтова. Као резултат враћа показивач на почетак резервисаног простора. Ако
се тражена количина меморије не може резервисати, као резултат се добије NULL
.
Иако имају исту улогу, ове две функције се разликују:
по броју параметара,
по особини:
calloc
иницијализује меморијске локације на нулу, док се заmalloc
каже да је садржај додељеног простора недефинисан.
Пример:
Наредбом p = (int*)calloc(n,sizeof(int))
резервише се меморијски простор за смештање
целобројног низа од n елемената. Адреса меморијског простора је смештена у показивач p:

Промена величине заузете меморије¶
Сада нешто о функцији realloc()
. Њу користимо када желимо да променимо величину меморије на
коју указује показивач р. Нова величина је приказана параметром br који представља број бајтова.
Њен општи облик је void realloc (void *p, int br)
. Показивач р мора да буде претходно дефинисан.
Нова величина меморије може бити већа или мања од старе. У случају повећања меморије, нови бајтови се додају на крају. У супротном, скраћивање се врши од краја, као што је случај у наредном примеру.
Ако желимо да меморијски простор смањимо за три елемента, то ћемо урадити следећом наредбом:
p = realloc(p, sizeof(int)*(n-3))
Функцијом p = realloc(p, sizeof(int)*(n-3))
резервисани простор смањујемо за 3 елемента и адресу смештамо у показивач р:

Ослобађање заузете меморије¶
Још једна функција којом се заокружује прича о динамичкој алокацији меморије је је free()
.
Служи за ослобађање меморије заузете функцијама malloc и calloc. Њен општи облик је
void free( void *p );
Аргумент р
је показивач на меморијски блок који треба ослободити и који смо претходно
дефинисали. Овом функцијом се простор који је додељен динамичким подацима ослобађа и
може се доделити новим динамичким подацима. Функција не враћа никакву вредност, само је
треба позвати када нам променљива више није потребна.
И најзад, све функције из динамичке алокације меморије ћемо илустровати једним примером.
Пример:
Одређивање суме елемената динамичког низа од n елемената.
int *p, n, i, s = 0;
p = (int*)malloc(n*sizeof(int));
или
p = (int*)calloc(n,sizeof(int));
Употребом једне од ове две функције резервише се меморијски простор за n елемената целобројног низа. Адреса почетка низа, односно његовог првог елемента се смешта у показивач p. Још једном напомињемо, ако користимо calloc, меморијске локације се иницијализују на нулу.
Наредбом p = (int*)calloc(n,sizeof(int)) резервишемо простор за n елемената и адресу смештамо у показивач р:

Према законима показивачке аритметике, адреса сваког елемента низа је p+i, па њу користимо у наредби scanf. Како је p+i адреса, знак & изостављамо.
for(i = 0; i < n; i++)
{
printf("a[%d] = ",i);
scanf("%d", p + i);
}
Приступ резервисаним меморијским локацијама и унос вредности у њих:

Пошто смо унели елементе низа, њиховим вредностима приступамо наредбом *(p+i),
for(i = 0; i < n; i++)
s += *(p + i);
printf("s = %d\n", s);
free(p);
Пошто смо прочитали вредности динамичког низа, сабрали их и приказали резултат, последњом наредбом free(p) ослобађамо меморију за унос нових, динамичких вредности.
Пример урађеног задатка који садржи резервисање меморије за смештање динамичког низа од пет елемента, чување адресе у показивачкој променљивој р, унос чланова низа и одређивање њихове суме би могао да изгледа овако:
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
int n = 5, i, s = 0;
int* p;
p = (int*)calloc(n, sizeof(int));
for (i = 0; i < n; i++)
{
printf("a[%d] = ", i);
scanf("%d", p + i);
}
for (i = 0; i < n; i++)
s += *(p + i);
printf("\ns = %d\n", s);
free(p);
return 0;
}
Резултат извршавања програма:
a[0]=7
a[1]=12
a[2]=36
a[3]=25
a[4]=18
s = 98