Динамичка алокација меморије

До сада си у својим програмима најчешће користио тзв. статичке променљиве. Зашто статичке? Зато што се на почетку програма резервише потребан меморијски простор, а он остаје резервисан и заузет до краја извршења програма.

  • 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 заузимају меморију током извршавања програма:

../_images/Picture31.png

Када су програми овако једноставни и када се заузима мала количина меморије, то и није неки проблем. Али ако се у програму користе веће количине података и ако постоји ограничење заузетости меморије, може настати проблем.

Као што смо рекли, идеја је да се меморија ослободи по извршењу неког дела програма и да се по потреби додели неким другим подацима x и y.

Ослобађање претходно заузете меморије и додела исте другим променљивим:

../_images/Picture32.png

Ово је само илустрација проблема – прича о динамичком заузимању меморије је мало сложенија и треба нам предзнање о показивачима.

Па да наставимо.

Статички подаци се смештају у део меморије који се назива статичка зона меморије.

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

Динамички подаци немају имена, већ се њима приступа помоћу показивача, односно адреса које се чувају у показивачима.

Функције за управљање меморијом у 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 бајтова у меморији и почетну адресу смешта у претходно дефинисан показивач р:

../_images/Picture33.png

Да се још једном подсетимо наредбе 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 би сликовито могао да изгледа овако:

../_images/Picture34.png

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

Изглед меморије после наредбе p = (int*)malloc(sizeof(int)):

../_images/Picture35.png

Једноставном наредбом, индиректним адресирањем, сада смештамо неку вредност у резервисани простор (5).

Изглед меморије после наредбе *p = 5:

../_images/Picture36.png

Заузимање меморије се може извршити и помоћу функције calloc():

void *calloc( int n, int br);

Функција резервише простор у меморији и иницијализује је на вредност 0 за n елемената величине br бајтова. Као резултат враћа показивач на почетак резервисаног простора. Ако се тражена количина меморије не може резервисати, као резултат се добије NULL.

Иако имају исту улогу, ове две функције се разликују:

  • по броју параметара,

  • по особини: calloc иницијализује меморијске локације на нулу, док се за malloc каже да је садржај додељеног простора недефинисан.

Пример:

Наредбом p = (int*)calloc(n,sizeof(int)) резервише се меморијски простор за смештање целобројног низа од n елемената. Адреса меморијског простора је смештена у показивач p:

../_images/Picture37.png

Промена величине заузете меморије

Сада нешто о функцији realloc(). Њу користимо када желимо да променимо величину меморије на коју указује показивач р. Нова величина је приказана параметром br који представља број бајтова. Њен општи облик је void realloc (void *p, int br). Показивач р мора да буде претходно дефинисан.

Нова величина меморије може бити већа или мања од старе. У случају повећања меморије, нови бајтови се додају на крају. У супротном, скраћивање се врши од краја, као што је случај у наредном примеру.

Ако желимо да меморијски простор смањимо за три елемента, то ћемо урадити следећом наредбом:

p = realloc(p, sizeof(int)*(n-3))

Функцијом p = realloc(p, sizeof(int)*(n-3)) резервисани простор смањујемо за 3 елемента и адресу смештамо у показивач р:

../_images/Picture38.png

Ослобађање заузете меморије

Још једна функција којом се заокружује прича о динамичкој алокацији меморије је је 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 елемената и адресу смештамо у показивач р:

../_images/Picture39.png

Према законима показивачке аритметике, адреса сваког елемента низа је p+i, па њу користимо у наредби scanf. Како је p+i адреса, знак & изостављамо.

for(i = 0; i < n; i++)
{
   printf("a[%d] = ",i);
   scanf("%d", p + i);
}

Приступ резервисаним меморијским локацијама и унос вредности у њих:

../_images/Picture40.png

Пошто смо унели елементе низа, њиховим вредностима приступамо наредбом *(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