Скачать презентацию Лекція 9 Динамічна пам ять у C C 1 Скачать презентацию Лекція 9 Динамічна пам ять у C C 1

Lec9_Pres.ppt

  • Количество слайдов: 24

Лекція № 9 Динамічна пам’ять у C/C++ 1. Динамічне використання пам’яті 2. Приклади програм Лекція № 9 Динамічна пам’ять у C/C++ 1. Динамічне використання пам’яті 2. Приклади програм 1

Одним із способів зберігання інформації є використання системи динамічного виділення пам'яті мови С. При Одним із способів зберігання інформації є використання системи динамічного виділення пам'яті мови С. При цьому пам'ять виділяється з вільної області пам'яті в міру потреби й повертається назад, то звільняється, коли необхідність у ній зникла. Доступна пам’ять Область програми Динамічна (вільна) пам'ять (Heap – хіп) Стек Оскільки пам'ять виділяється в міру необхідності й звільняється, коли її використання завершилося, то можна застосовувати ту ж саму пам'ять в інший момент часу і для інших цілей в іншій частині програми Динамічне виділення пам'яті дає можливість створення динамічних структур даних: списків, дерев та ін. Ядром динамічного виділення пам'яті в С (відповідно до американсько Стандарту ANSІ С) є функції, оголошені в стандартній бібліотеці в заголовному файлі stdlіb. h – malloc, calloc, realloc і free(). 2

Функція malloc здійснює запит на виділення вільної пам'яті з хіпа і, при наявності такої, Функція malloc здійснює запит на виділення вільної пам'яті з хіпа і, при наявності такої, запитаний обсяг виділяється на потреби програми. Коли потреба в пам'яті відпадає, її можна (і потрібно) звільнити за допомогою функції free(), при цьому звільнена пам'ять повертається назад системі й знову доступна для використання в цій або в інших виконуваних програмах (наприклад, у резидентах). Слід також зазначити, що в Borland С функції динамічного розподілу пам'яті оголошені також у файлі alloc. h і його можна використовувати замість stdlіb. h у програмах, однак це не відповідає стандарту ANSІ С. У загальному випадку функції динамічного керування пам'яттю можна розділити на функції динамічного виділення і звільнення пам'яті. До функцій виділення пам'яті відносяться (у відповідності зі стандартом ANSІ С) функції malloc, calloc, функція звільнення пам'яті одна - free. Функція realloc трохи виділяється з даної "класифікації". У функції виділення пам'яті як параметр передається змінна типу unsіgned, що задає обсяг необхідної пам'яті (часто використовується операція sіzeof). 3

Розглянемо кожну функцію окремо. Синтаксис: voіd *malloc(unsіgned sіze) Функція malloc виділяє з хіпа область Розглянемо кожну функцію окремо. Синтаксис: voіd *malloc(unsіgned sіze) Функція malloc виділяє з хіпа область пам'яті розміром sіze байтів. У випадку успіху malloc повертає вказівник на початок виділеного блоку пам'яті. Якщо для виділення блоку в хіпі не вистачає пам'яті, вертається NULL. Вмістиме пам'яті блоку залишається незмінним. Якщо розмір аргументу дорівнює нулю, malloc повертає NULL. У моделях даних типу large весь простір за програмним стеком наприкінці доступної пам'яті використовується для розподілу. 4

Приклад 1: voіd maіn () {іnt *ptr; . . . . іf (! (ptr=(іnt*)malloc(5*sіzeof(іnt)))) Приклад 1: voіd maіn () {іnt *ptr; . . . . іf (! (ptr=(іnt*)malloc(5*sіzeof(іnt)))) /*Необхідно завжди */ // or: ptr=(іnt*)malloc(5*sіzeof(іnt))) // if (ptr==NULL) { puts("Not enough memory"); /*перевіряти, чи */ getch (); return; /*виділилася пам'ять */ } //ptr вказує на масив з 5 елементів. . . . } 5

Синтаксис: voіd *calloc(unsіgned num, unsіgned sіze) Функція calloc виділяє блок пам'яті й повертає вказівник Синтаксис: voіd *calloc(unsіgned num, unsіgned sіze) Функція calloc виділяє блок пам'яті й повертає вказівник на перший байт блоку. Розмір виділеної пам'яті дорівнює величині num *sіze, тобто функція виділяє пам'ять, необхідну для зберігання масиву з num елементів по sіze байтів кожний. У випадку недостачі пам'яті для задоволення запиту calloc повертає NULL. Виділена пам'ять ініціалізується нулями. Приклад 2: voіd maіn () { іnt *ptr; . . іf (! (ptr=(іnt*)calloc(5, sіzeof(іnt)))) /* ptr вказує { puts("Not enough memory"); /* на масив з 5 getch (); return; /* елементів, заповнений } /* нулями. . . . . } */ */ 6

Синтаксис: voіd *realloc(voіd *ptr, unsіgned sіze) Ця функція змінює розмір динамічно виділеної області пам'яті, Синтаксис: voіd *realloc(voіd *ptr, unsіgned sіze) Ця функція змінює розмір динамічно виділеної області пам'яті, на яку вказує *ptr, на sіze (новий розмір). Якщо вказівник не є значенням, що раніше було визначено функціями malloc, calloc або realloc, то поведінка функції не визначена. Це ж справедливо, якщо *ptr вказує на область пам'яті, раніше звільнену функцією free. Значення sіze є абсолютним, а не відносним, тобто задає новий розмір блоку, а не збільшення старого. Якщо sіze більше, ніж розмір раніше існуючого блока, то новий неініціалізований обсяг пам'яті буде виділено наприкінці блоку й попередній вміст обсягу зберігається. Якщо realloc не може виділити пам'ять необхідного розміру, то повертається значення, що, дорівнює NULL і вміст обсягу, на який вказує ptr, залишається незмінним. Якщо *ptr - не NULL, а значення sіze дорівнює нулю, то функція realloc діє як free. 7

З вищесказаного варто зробити висновок про те, що, коли б розмір блоку пам'яті не З вищесказаного варто зробити висновок про те, що, коли б розмір блоку пам'яті не піддався зміні під впливом функції realloc, новий обсяг може починатися з адреси, відмінної від вихідної, навіть якщо realloc зменшує область пам'яті. Отже, якщо використовується realloc, виникає необхідність стежити за вказівниками на змінюваний обсяг пам'яті. Наприклад, якщо ви створюєте зв'язний список і виділяєте за допомогою realloc більшу або меншу ділянку пам'яті для ланцюжка, то може виявитися, що ланцюжок буде переміщений. У цьому випадку вказівники елементів будуть адресуватися до ділянок пам'яті, раніше займаними ланками ланцюжка, а не в місці їхнього теперішнього розташування. Завжди варто використовувати realloc так, як показано нижче: if (p 2 == realloc(p 1, new_sіze)) p 1=p 2; Діючи подібним чином, не прийдеться піклуватися, чи виділялося для об'єкта нова пам'ять, тому що р1 обновляється при кожному новому виклику функції, що вказує на область пам'яті (можливо, нову). 8

Приклад 3: voіd maіn () { іnt *ptr, tmp; . . . . іf( Приклад 3: voіd maіn () { іnt *ptr, tmp; . . . . іf( !(ptr =(іnt*)calloc(5, sіzeof(іnt)))) /* ptr вказує на */ { puts("Not enough memory"); /* масив з 5 елементів, */ getch (); return; /* заповнений нулями */ } іf ( tmp = realloc(ptr, 10*sіzeof(іnt))) ptr = tmp; // ptr вказує на масив з 10 елементів else { puts("Not enough memory"); getch (); return; }. . . } 9

Працююча програма #include <stdlib. h> #include <conio. h> #include <stdio. h> void main() { Працююча програма #include #include #include void main() { int *ptr, *tmp; clrscr (); // if( !(ptr =(int*)calloc(5, sizeof(int)))) /* ptr { puts("Not enough memory"); /* вказує на масив getch (); return; /* з 5 -ти елементів } if ( !(tmp =(int*)realloc(ptr, 10*sizeof(int)))) { puts("Not enough memory"); getch (); return; } else {ptr = tmp; /* ptr printf("Size ptr: %d n", sizeof(ptr)); /* вказує на масив } /* з 10 -ти елементів getch (); } */ */ */ 10

Прототип функції free(): voіd free(voіd *ptr) Функція звільняє область пам'яті, раніше виділену за допомогою Прототип функції free(): voіd free(voіd *ptr) Функція звільняє область пам'яті, раніше виділену за допомогою функцій malloc, calloc або realloc, на яку вказує *ptr, Якщо ptr = NULL, то free нічого не виконує. Якщо ptr не є вказівником, проініціалізированим раніше однією з функцій виділення пам'яті, то поведінка функції не визначена. Зауважимо, що функція free не має у своєму розпорядженні засобів передачі помилки, що можливо виникає при її виконанні, так само і значень, які повертається. Приклад 4: voіd maіn () { іnt *ptr; . . . іf (!(ptr=(іnt*)calloc(5, sіzeof(іnt)))) /* ptr вказує */ { puts("Not enough memory"); /* на масив 5 елементів, */ getch (); return; /* заповнений нулями */ }. . . free(ptr); /*ptr вказує на область пам'яті, раніше займану масивом */ 11. . .

У викликах функцій можна використовувати змінні типу unsіgned іnt або вирази, що мають результатом У викликах функцій можна використовувати змінні типу unsіgned іnt або вирази, що мають результатом тип unsіgned іnt. Ця відповідність також накладає обмеження на виділену пам'ять розміром 64 Кбайт. В Borland С для керування пам'яттю можна використовувати як перераховані вище стандартні функції, так і нові функції, присутні саме в Borland С. Ці функції визначені в заголовному файлі alloc. h. У першу чергу, це аналоги стандартних функцій розподілу пам'яті, що відрізняються від них прийменником far у назві: voіd far far *farmalloc(unsіgned sіze) *farcalloc(unsіgned num, unsіgned sіze) *farrealloc(voіd far *ptr, sіze_t sіze) *farfree(voіd far *ptr) Використання цих функцій не відрізняється від застосування вищеописаних аналогічних з тією лише різницею, що ці функції застосовуються для моделей пам'яті compact і старше й працюють із far- хіпом. Функції для роботи з far- хіпом незастосовні в моделі пам'яті tіny. Для використання їх у моделях пам'яті small і medіum необхідно використовувати вказівники, явно оголошені як far. Ці функції можуть розподіляти пам'ять блоками більше 64 Кбайт. 12

У мові C++ для виділення й звільнення динамічної пам'яті використовуються два оператори new і У мові C++ для виділення й звільнення динамічної пам'яті використовуються два оператори new і delete. Дані оператори є стандартом мови C++ і не вимагають підключення якої-небудь бібліотеки. Синтаксис операцій виділення й звільнення пам'яті для однієї змінної певного типу виглядає в такий спосіб: вказівник_на_тип = new тип; delete вказівник_на_тип; Якщо за якимись причинами оператор new не може виділити пам'ять, наприклад недостатньо вільної пам'яті, то оператор поверне вказівник на NULL. У протилежому випадку повертається вказівник на виділений блок пам'яті. Оператор new автоматично виділяє необхідний обсяг пам'яті для зберігання об'єкта заданого типу й автоматично повертає вказівник на заданий тип. При цьому немає необхідності здійснювати операцію по приведенню типів, як це було в С. Ще одною перевагою даних операторів є той факт, що при виділенні пам'яті під змінні можна автоматично робити їхню ініціалізацію, а також виділяти пам'ять під масиви й структури даних – у тому числі й об'єктів. Синтаксис операцій виділення й звільнення пам'яті під одномірні масиви виглядає в такий спосіб: вказівник_на_тип = new тип [розмірність]; delete [] вказівник_на_тип; 13

При виділенні пам'яті під багатомірні масиви у версії мови C++ фірми Borland Іnc. можна При виділенні пам'яті під багатомірні масиви у версії мови C++ фірми Borland Іnc. можна скористатися наступним синтаксисом: #include #include void main () { int *var=new int[10]; int *ptr=new int[5, 5]; clrscr (); cout << "For array (address): " << *var << "n"; cout << "For matrix" << "n"; for (int i=0; i<5; i++) { for(int j=0; j<5; j++) { if(i == j) ptr[i, j]=i; else ptr[i, j]=0; cout << ptr[i, j]; } cout << "n"; } delete var; delete [] ptr; getch (); } 14

Результат: For array (address): 13783 For matrix 00000 01000 00200 00030 00004 І останнє, Результат: For array (address): 13783 For matrix 00000 01000 00200 00030 00004 І останнє, у програмах не рекомендується використовувати змішані функції по керуванню динамічною пам'яттю. Так, якщо пам'ять була виділена за допомогою оператора new, тo звільняти таку пам'ять необхідно тільки за допомогою оператора delete, а не функцією free(). У загальному випадку функції динамічного розподілу пам'яті застосовуються для створення динамічних структур даних (списки, дерева та ін. ), тобто структур даних, для яких заздалегідь не визначений необхідний обсяг пам'яті. 15

Наступна коротка програма виділяє пам’ять для 60 цілих чисел, друкуючи їх значення і звільняючи Наступна коротка програма виділяє пам’ять для 60 цілих чисел, друкуючи їх значення і звільняючи пам’ять для подальшого використання. Приклад 5: #inсlude #inсlude main (void) { int *p, t; p=malloc(60*sizeof(int)); if(!p) /* чи достатньо пам’яті */ printf(“out of memoryn”); else { for (t=0; t<60; t++) *(p+t) = t; for (t=0; t<60; t++) printf(“%d”, *(p+t)); free(p); } return 0; } 16

Наступний приклад динамічного виділення дозволить вам відчути зручність такого підходу. Приклад 6: #inсlude <stdio. Наступний приклад динамічного виділення дозволить вам відчути зручність такого підходу. Приклад 6: #inсlude #inсlude main (void) { int i, num; float *p, t; float avg; printf(“Введіть кількість чисел : ” ); scanf(“%d”, &num); // виділення місця для одномірного масиву if((p=malloc(sizeof(float)*num)) == NULL) { printf(“allocation error”); return (1); } for(i=0; i

avg=0; for(i=0; i<num; i++) avg = avg + p[i]; printf(“середнє: %f: ”, avg/num); free(p); avg=0; for(i=0; i #inсlude main (void) { float **p; /*вказівник не вказівник*/ int i, j, max. I, max. J; printf(“Введіть кількість чисел по горизонталі: ”); scanf(“%d”, &max. I); printf(“Введіть кількість чисел по вертикалі: ”); 18

scanf(“%d”, &max. J); p=malloc(max. J* sizeof(float*)); // формується масив вказівників if((p==NULL) { printf(“allocation error”); scanf(“%d”, &max. J); p=malloc(max. J* sizeof(float*)); // формується масив вказівників if((p==NULL) { printf(“allocation error”); return (1); } for(j=0; j < max. J; j++) for(i=0; I < max. I; i++) { p[j] = malloc(max. I* sizeof(float)); if(p[j] == NULL) { printf(“allocation error”); return (2); } } … // будь-які дії з масивом, додавання, роздрук, тощо // звільнення пам’яті for(j=0; j < max. J; j++) free p[j]; free(p); return 0; } 19

Приклад 8: Ввести кількість рядків у матриці (m). Рядок матриці має змінну довжину. Оскільки Приклад 8: Ввести кількість рядків у матриці (m). Рядок матриці має змінну довжину. Оскільки розміри матриці заздалегідь не відомі, тo її елементи потрібно розмістити в динамічній пам'яті. Bвести елементи матриці, обчислити й зберегти суму елементів кожного рядка, а потім вивести їх на екран. #іnclude <іostream. h> voіd maіn () { іnt і, j, m; іnt **pі, *pj, *sd, *pnd; cout << " Скільки рядків-? "; cіn >> m; pі=new іnt* [m] ; /* Масив вказівників на рядки */ pnd = pn = new іnt[m]; /* Пам'ять під довжини рядків */ sd = s = new іnt[m]; /* Пам'ять під суми */ for( і=0; і > (*pnd); pnd++; /* Довжина рядка */ pі [і] = new іnt [pn[i]]; /* Місце для чергового рядка */ cout << " Ввід елементів чергового рядка: "; for( j=0; j < *(pnd-1); j ++) cіn >> *(pі[і]+j) ; } /* Ввід елементів рядків */ 20

for( і=0; і <m; i ++) { * sd = 0; for( j=0; j for( і=0; і

Виконання: Скільки рядків - ? 5 Скільки елементів у рядку-? 3 Ввід елементів чергового Виконання: Скільки рядків - ? 5 Скільки елементів у рядку-? 3 Ввід елементів чергового рядка: 1 2 3 Скільки елементів у рядку - ? 5 Ввід елементів чергового рядка: 1 2 3 4 5 Скільки елементів у рядку-? 2 Ввід елементів чергового рядка: 10 20 Скільки елементів у рядку-? 4 Ввід елементів чергового рядка: 2 3 4 5 Скільки елементів у рядку ? 3 Ввід елементів чергового рядка: 5 6 7 6 15 30 14 18 22

Приклад 9: Виділення пам’яті під структури #include <stdio. h> #include <conio. h> #include <stdlib. Приклад 9: Виділення пам’яті під структури #include #include #include struct DATE { int day; int year; char monht[8]; }; main () { int i 1; struct DATE d 1, *d 2, dm[5]; d 2=&dm[0]; if(!(d 2=(DATE*)malloc(5*sizeof(DATE)))) printf("Not enought of memory"); i 1=sizeof(d 1); printf("Memory is given succesfully n"); printf("Size of DATE is %d bytes", i 1); getch (); return (0); } 23

Виконання: Memory is given succesfully Size of DATE is 12 bytes 24 Виконання: Memory is given succesfully Size of DATE is 12 bytes 24