Դժվար կլիներ կառուցել քիչ թե շատ պիտանի ծրագրեր, եթե C լեզուն թույլ տար օգտագործել միայն ավտոմատ և ստատիկ փոփոխականներ կամ զանգվածներ (որոնց զբաղեցրած հիշողության չափը որոշվում է կոմպիլյացիայի ժամանակ)։ Ավելի հաճախ են հանդիպում այն դեպքերը, երբ անհրաժեշտ հիշողության չափը հայտնի է դառնում ծրագրի կատարման ընթացքում։
Օրինակ, ինչպե՞ս գրել ծրագիր, որն օգտագործողից պահանջում է ներմուծել իրական թվերի հաջորդականություն, ապա արտածում է այդ նույն թվերը՝ հակառակ կարգով։ Վատագույն դեպքն այն է, որ հայտարարել հնարավորինս մեծ զանգված և նրա մեջ լցնել թվերը։ Բայց միշտ հնարավոր է, որ օգտագործողի խելքին փչի ներմուծել ավելի շատ թվեր, քան զանգվածի չափն է։ Ավելի լավ տարբերակն այն է, որ ծրագրում սահմանված զանգվածն ունենա փոփոխվող, չֆիքսված չափ։
C լեզվի ստանդարտ գրադարանի stdlib.h
ֆայլում հայտարարված են դինամիկ հիշողության հետ աշխատող calloc
, realloc
, malloc
և free
ֆունկցիաները։ Առաջին երեքը նախատեսված են համակարգից նոր հիշողության տիրույթ առանձնացնելու համար, իսկ չորրորդը՝ առանձնացված հիշողության տիրույթը համակարգին վերադարձնելու համար։
Եվ այսպես, գրում եմ մի ծրագիր, որը ներմուծման ստանդարտ հոսքից կարդում է իրական թվերի հաջորդականություն, այնուհետև, հենց որ հանդիպում է EOF
կոդը, այդ թվերն արտածում է ներմուծվածին հակառակ կարգով։
Թվերը ժամանակավորապես պահելու համար կօգտագործեմ numbers
դինամիկ զանգվածը, որի ընթացիկ չափը ցույց է տալու size
ամբողջ թիվը։ Սկզբի համար size
փոփոխականին կտամ 8 արժեքը։
int size = 8;
double* numbers = calloc( size, sizeof(double) );
calloc
ֆունկցիայի առաջին արգումենտը տարրերի քանակն է, իսկ երկրորդը՝ մեկ տարրի չափը։ Ֆունկցիան վերադարձնում է զրոներով արժեքավորված հիշողության տիրույթ՝ տրված քանակով և տրված չափի տարրերի համար։
Հետո ուզում եմ կազմակերպել նախապայմանով ցիկլ, որը կկարդա թվերը, քանի դեռ չի հանդիպել EOF
կոդը։ C լեզվում նախապայմանով ցիկլը կազմակերպվում է while
հրամանով։
while( ⟨կրկնման պայման⟩ )
⟨մարմին⟩
Քանի դեռ ճշմարիտ է ⟨կրկնման պայման⟩
-ը կատարվում են ⟨մարմին⟩
բլոկի հրամանները։
Հետո while
հրամանով կազմակերպեմ մի անվերջ ցիկլ, որի մարմնում կարդալու եմ թվերը։ Այդ անվերջ ցիկլն ավարտվելու է այն դեպքում, երբ scanf
ֆունկցիան վերադարձնում է EOF
(end of file) արժեքը։
int count = 0; /* ներմուծված թվերի հաշվիչ */
while( EOF != scanf( "%lf", &numbers[count] ) )
if( ++count == size ) {
size *= 2;
numbers = realloc( numbers, size * sizeof(double) );
}
Ցիկլի հերթական իտերացիայում, count
փոփոխականի արժեքը համեմատվում է զանգվածի ընթացիկ չափի հետ։ Եթե դրանք հավասար են, ուրեմն զանգվածի բոլոր դիրքերը զբաղված են և պետք է մեծացնել նրա չափը։ size *= 2
արտահայտությամբ, որը համարժեք է size = size * 2
արտահայտությանը, ես կրկնապատկում եմ զանգվածի ընթացիկ չափը որոշող փոփոխականի արժեքը։ Իսկ numbers
զանգվածը ընդլայնում եմ realloc
ֆունկցիայով։ Վերջինս ընդլայնում է malloc
, calloc
կամ realloc
ֆունկցիաներով առանձնացված հիշողության տիրույթը՝ պահպանելով տվյալները։ Այն իր արգումենտում ստանում է ընդլայնվելիք հիշողության հասցեն և նոր չափը։
Երբ ներմուծման ցիկլը ավարտվում է (Linux համակարգրում EOF
սիմվոլը ներմուծվում է ստեղնների Ctrl+D
համադրմամբ), մեկ այլ while
ցիկլով արտածում եմ numbers
զանգվածի պարունակությունը։
while( --count >= 0 )
printf( "%lf\n", numbers[count] );
Ամենավերջում պետք է ազատել ծրագրի աշխատանքի ընթացքում համակարգից վերցրած դինամիկ հիշողությունը։ free
ֆունկցիան արգումենտում ստանում malloc
, calloc
կամ realloc
ֆունկցիաներով առանձնացված հիշողության տիրույթի հասցեն և ազատում ու համակարգին է վերադարձնում այդ տիրույթը։
free( numbers );
Նկարագրված ծրագիրը գրառում եմ prog07a.c
ֆայլում, կումպիլյացնում եմ ու գործարկում։ Հետո ներմուծում եմ 10 տարբեր թվեր ու սեղմում եմ Ctrl+D
, որ նշանակում է ներմուծման ավարտ (EOF
կոդն է)։ Ծրագիրն անմիջապես հակառակ կարգով արտածում է ներմուծված թվերը։
Որպեսզի համոզվեմ, որ free
ֆունկցիան իսկապես համակարգին է վերադարձրել calloc
և realloc
ֆունկցիաներով ստացված հիշողության տիրույթը, կօգտագործեմ Valgrind ծրագիրը։ Ի թիվս այլ բաների, Valgrind-ը հնարավորություն է տալիս բացահայտել հիշողության կորուստները։
valgrind ./prog07a
...
Բացի calloc
և realloc
ֆունկցիաներից հիշողության նոր տիրույթ կարելի է առանձնացնել նաև malloc
ֆունկցիայով։ Այն վերադարձնում է հիշողության տիրույթ, որի չափը ստանում է իր միակ արգումենտով։ Միակ տարբերությունն այն է, որ malloc
ֆունկցիան զրոներով չի արժեքավորում առանձնացված տիրույթը։ Վերը բերված օրինակում calloc
ֆունկցիայի կիրառությունը կարելի է փոխարինել malloc
ֆունկցիայի հետևյալ կիրառությամբ.
double* numbers = malloc( size * sizeof(double) );
malloc
ֆունկցիան ավելի հաճախ օգտագործվում է ստրուկտուրաների նմուշների դինամիկ ստեղծման համար։ Օրինակ, ենթադենք իրական թվեր պարունակող որոնման բինար ծառի հանգույցը սահմանված է որպես node
ստրուկտուրա.
/* որոնման բինար ծառի հանգույց */
struct node {
double data; /* արժեքի դաշտ */
struct node* left; /* ձախ ենթածառ */
struct node* right; /* աջ ենթածառ */
};
Սահմանեմ create_node
ֆունկցիան, որը ստեղծում է նոր հանգույց, դրա data
դաշտում գրում է տրված արժեքը և վերադարձնում է այդ հանգույցի ցուցիչը։
struct node* create_node( double val )
{
struct node* no = malloc(sizeof(struct node));
no->data = val; no->left = NULL; no->right = NULL;
return no;
}
Եթե ինչ-որ պատճառով ձախողվում է պահանջվող չափի հիշողության տիրույթի առանձնացումը, ապա malloc
, calloc
և realloc
ֆունկցիաները վերադարձնում են NULL
արժեքը։ Եվ այս փաստը պետք է հաշվի առնել ու ծրագրերը լրացել համապատասխան ստուգումներով։ Օրինակ.
double* numbers = calloc( size, sizeof(double) );
if( numbers == NULL ) {
puts( "Հնարավոր չէ հատկացնել պահանջվող հիշողությունը։" );
exit( 1 );
}