Розділ 12. Динамічна пам’ять
12.1. Загальні поняття
Динамічна пам’ять (heap, купа) – це пам’ять, в якій змінні розміщуються динамічно. Основна потреба у динамічному виділенні пам’яті виникає тоді, коли розмір або кількість даних, які необхідно виділити чи використати, наперед невідомі і визначаються у процесі виконання програми.
Засоби динамічного управління пам’яттю дають змогу розширити можливості програмування. Під час свого виконання програма отримує можливість самостійно визначати потрібні розміри пам’яті і за потреби в реальному часі створювати та знищувати динамічні об’єкти.
Основні відмінності динамічного масива від статичного такі:
- пам’ять для динамічного масива виділяється під час виконання програми за допомогою певних функцій;
- кількість елементів динамічного масива може бути задано змінною, але в програмі вона обов’язково має бути визначена до виділення пам’яті для масива.
Динамічно створені об’єкти даних, у тому числі масиви, суттєво відрізняються від змінних, пам’ять для яких виділяється автоматично. Динамічні об’єкти не є локальними для певної функції (локальною може бути змінна, що містить адресу такого об’єкта). Вони можуть створюватись в одній функції, опрацьовуватись в другій (якщо перша повідомить їй адресу об’єкта), а знищуватись в третій. Таким чином, доступ до динамічного об’єкта має будь-яка функція програми, якій відома його адреса.
Час життя динамічного об’єкта – це час від моменту його створення до знищення, тобто динамічно виділена пам’ять існує поки її не звільнити. Якщо динамічно виділену пам’ять не звільнити, то в операційній системі може виникнути ситуація нестачі вільної пам’яті.
12.2. Функції для роботи з динамічною пам’яттю
Динамічне управління пам’яттю в мові С/С++ виконується за допомогою стандартних функцій, оголошення яких розміщені в заголовочному файлі stdlib.h (alloc.h або malloc.h).
Для роботи динамічною пам’яттю в мові С використовують такі функції:
1) функція void *malloc(sizet size) виділяє ділянку пам’яті для вказаної кількості байтів – size. Функція malloc() повертає вказівник на початок виділеної пам’яті. Якщо вільної пам’яті для розміщення заданої кількості байтів недостатньо або аргумент size дорівнює 0, то функція malloc() повертає значення NULL. Після успішного виконання функції malloc() вміст виділеної пам’яті залишається незмінним, тобто після виділення певної ділянки пам’яті на ній можуть залишитись дані, що використовувались раніше іншою програмою;
2) функція void *calloc(sizet nitems, sizet size) виділяє ділянку пам’яті розміром nitems*size, де nitems – кількість елементів, size – кількість байтів одного елемента, і повертає вказівник на неї. На відміну від функції malloc() кожний елемент виділеної ділянки пам’яті ініціалізовується значенням 0. Функція calloc() повертає значення NULL, якщо для виділення нової ділянки не вистачає пам’яті, або якщо значення nitems чи size дорівнюють 0;
3) функція free() використовується для звільнення динамічної пам’яті, виділеної за допомогою функцій malloc() і calloc(); вона має такий синтаксис:
void free(*<ім’я_вказівника>);
де *<ім’я_вказівника> – вказівник на ділянку, яку потрібно звільнити.
4) функція void *realloc(*bl, size) встановлює новий розмір в size байтів для раніше виділеної ділянки пам’яті з адресою *bl. Якщо ця зміна відбулась успішно, то функція realloc() повертає вказівник на виділену ділянку пам’яті, інакше вона повертає значення NULL. Якщо параметр *bl дорівнює NULL, то функція realloc() виконує ті ж дії, що й malloc(). Якщо size дорівнює 0, то ділянка пам’яті, виділена за адресою *bl, звільняється і функція повертає значення NULL.
Оскільки ці функції повертають вказівник на тип void, який слід присвоїти змінній певного типу для подальших операцій з нею, то виникає потреба в явному перетворенні типів.
Приклад 12.1. Використання функції malloc():
int k = 1;
int *m = (int*) malloc(k * sizeof(int)); // виділення пам’яті
*m = 7; // ініціалізація динамічної змінної значенням 7
free(m); // звільнення пам’яті
Приклад 12.2. Використання функції сalloc():
int k = 1;
int *c = (int*) calloc(k, sizeof(int)); // виділення пам’яті
*c = 4; // ініціалізація динамічної змінної значенням 4
free(c); // звільнення пам’яті
У мові С++ для виділення динамічної пам’яті використовується функція new, яка має такий синтаксис:
<тип_даних> *<змінна> = new <тип_даних> (<змінна>);
Динамічно виділена пам’ять за допомогою функції new не звільняється автоматично, тому її обов’язково потрібно звільняти самостійно за допомогою функції delete. Синтаксис функції delete такий:
delete [] <вказівник>
Параметр [] (квадратні дужки) у функції delete необов’язковий. При цьому квадратні дужки повинні бути порожніми, оскільки операційна система контролює кількість виділеної пам’яті для певного об’єкта і їй відома кількість байтів, яку потрібно звільнити.
Приклад 12.3. Виділити пам’ять для цілого числа за допомогою new:
- 1) виділити місце у пам’яті для цілого числа:
int *n = new int; // виділення пам’яті
*n = 5; // ініціалізація динамічної змінної значенням 5
delete n; // звільнення пам’яті
Доступ до числа здійснюється через вказівник на нього.
- 2) виділити пам’ять для цілого числа та ініціалізувати його значенням числа k:
int k = 10;
// виділення пам’яті та ініціалізація динамічної змінної числом 10
int *g = new int(k);
(*g) -= 3; // зменшення значення динамічної змінної на 3
delete g; // звільнення пам’яті
Доступ до числа здійснюється через вказівник на нього.
12.3. Динамічні одновимірні масиви
Робота з динамічними масивами виконується за допомогою змінних типу вказівник. Після створення масива така змінна вказує на його початок, тобто містить адресу його першого елемента.
Розмір динамічного масива задається не константою, а змінною, значення якої під час виконання програми вводить з клавіатури користувач або воно обчислюється за певними формулами.
12.3.1. Оголошення динамічного одновимірного масива
Розглянемо декілька способів оголошення динамічного одновимірного масива.
12.3.1.1. Використання функції malloc()
Синтаксис оголошення динамічного одновимірного масива за допомогою функції malloc() такий:
<тип> *<ім’я> = (<тип>*) malloc (<кількість_елементів> * sizeof(<тип>));
де параметр <кількість_елементів> – це кількість елементів, для яких потрібно виділити пам’ять, а параметр sizeof(<тип>) – це розмір одного елемента певного типу в байтах.
Оскільки функція malloc() наперед не знає, дані якого типу будуть розміщені у динамічно виділеній області пам’яті, то значення, яке вона повертає, має тип абстрактного вказівника void*. А для того, щоб використовувати такий вказівник як масив, його слід перетворити до відповідного типу за допомогою операції перетворення типу.
Приклад 12.4. Оголосити динамічний одновимірний масив за допомогою malloc:
#include <iostream>
#include <conio.h>
using namespace std;
int main() {
int Count; // кількість елементів масива
printf("Enter number of elements: "); scanf("%d", &Count);
// створення динамічного одновимірного масива
double *p = (double*) malloc(Count * sizeof(double));
// перевірка, чи масив створився
if (p == NULL) {
printf("\nMemory error!\n");
return -1; // якщо масив не створився, виконується вихід з програми
}
// введення масива
printf("\n\nEnter elements:\n");
for (int i = 0; i < Count; i++) {
printf("p[%d]= ", i);
scanf("%lf", &p[i]);
}
// подальше опрацювання масива
// звільнення пам’яті, виділеної під масив
free(p);
getch();
return 0;
}
12.3.1.2. Використання функції calloc()
Синтаксис оголошення динамічного одновимірного масива за допомогою функції calloc такий:
<тип> *<ім’я> = (<тип>*) calloc (<кількість_елементів>, sizeof(<тип>));
де параметр <кількість_елементів> – це кількість елементів, для яких потрібно виділити пам’ять, а параметр sizeof(<тип>) – це розмір одного елемента певного типу в байтах.
Приклад 12.5. Оголосити динамічний одновимірний масив за допомогою calloc:
#include <iostream>
using namespace std;
int main() {
int Count; // кількість елементів масива
cout << "Enter number of elements: "; cin >> Count;
// створення динамічного одновимірного масива
double *p = (double*) calloc(Count, sizeof(double));
// перевірка, чи масив створився
if (p == NULL) {
cout << "\nMemory error!\n";
return -1; // якщо масив не створився, виконується вихід з програми
}
// введення масива
cout << "\n\nEnter elements:\n";
for (int i = 0; i < Count; i++) {
cout << "p[" << i << "]= ";
cin >> p[i];
}
// подальше опрацювання масива
// звільнення пам’яті, виділеної під масив
free(p);
system("pause");
return 0;
}
12.3.1.3. Використання функції new()
Синтаксис оголошення динамічного одновимірного масива за допомогою функції new такий:
<тип_даних> *<ім’я_вказівника> = new <тип_даних> [кількість_елементів];
Оператор new виділяє місце в пам’яті для певної кількості елементів певного типу, а адреса цієї ділянки пам’яті записується у змінну-вказівник.
Приклад 12.6. Оголосити динамічний одновимірний масив за допомогою new:
#include <iostream>
using namespace std;
int main() {
int Count; // кількість елементів масива
cout << "Enter number of elements: ";
cin >> Count;
// створення динамічного одновимірного масива
int *b = new int [Count];
// перевірка, чи масив створився
if (b == NULL) {
cout << "\nMemory error!\n";
return -1; // якщо масив не створився, виконується вихід із програми
}
// введення масива
cout << "\n\nEnter elements:\n";
for (int i = 0; i < Count; i++) {
cout << "b[" << i << "]=";
cin >> b[i];
}
// подальше опрацювання масива
// звільнення пам’яті, виділеної під масив
delete [] b;
system("pause");
return 0;
}
Для доступу до значень елементів такого масива використувується одна з наступних форм:
а) індексна форма: b[i];
б) вказівникова форма: *(b+i), що еквівалентно b[i].
Приклад 12.7. Обчислити добуток елементів динамічного одновимірного масива:
#include <iostream>
#include <iomanip>
#include <time.h>
using namespace std;
// оголошення функції dob()
double dob(double*, int);
// визначення функції main()
void main() {
int i, count;
cout << "Enter number of elements: ";
cin >> count;
srand((unsigned)time(NULL));
// створення динамічного одновимірного масива
double *a = new double [count];
// ініціалізація масива
for(i = 0; i < count; i++)
a[i] = rand() % 100 + 1;
// виведення масива
for(i = 0; i < count; i++)
cout << setw(7) << a[i];
cout << endl;
// виведення результату
cout << "Multiply = " << dob(a, count);
// звільнення пам’яті, виділеної під масив
delete []a;
system("pause");
}
// визначення функції dob()
double dob(double *a, int colCount) {
int i;
double p = 1;
for(i = 0; i < colCount; i++)
p *= a[i];
return p;
}
Приклад 12.8. Змінити розмір виділеної пам’яті для одновимірного масива з 10-ти елементів до 20-ти елементів:
int Count = 10, newCount = 20;
// оголошення та створення одновимірного масива
float *a = (float*) calloc(Count, sizeof(float));
// зміна розміру масива
a = (float*) realloc(a, newCount);
12.4. Динамічні двовимірні масиви
12.4.1. Динамічний двовимірний масив як одновимірний
Двовимірний динамічний масив займає в пам’яті сусідні комірки, тобто зберігається, як одновимірний масив (рис. 12.1).
(Для ознайомлення з повним текстом статті необхідно залогінитись)