Операції роботи з пам'яттю
В C++ є можливість розподіляти пам'ять під час виконання. Вільна пам'ять у програмах мовою С++ виділяється за допомогою операції new, що застосовується до специфікації типу (абстрактному імені типу). У цьому випадку виділиться пам'ять, достатня для розміщення об'єкта такого типу й у результаті буде повернутий покажчик на виділену пам'ять, наприклад:
int *pi = new int;
Тут виділена пам'ять для об'єкта типу int. Тип значення, що повертається, «покажчик на int». Порожній покажчик означає невдале завершення операції. Цей випадок виникає, коли недостатній обсяг пам'яті або занадто велика фрагментація області, що розподіляється. Треба, однак, відзначити, що оператор new не очищає виділену пам'ять і містить "сміття ".
Операція new зручна тим, що вона як параметр одержує тип створюваного об'єкта, а не його розмір і повертає покажчик на заданий тип, не вимагаючи приведення типу. С++ допускає явну ініціалізацію пам'яті, яка виділяється, для об'єкта будь-якого типу із простим ім'ям:
int *pi = new int(100); // *pi == 100 - значення, записане у виділену динамічну пам'ять.
Масив виділяється у вільній пам'яті за допомогою наступної за специфікацією типу розмірності, що укладена у квадратні дужки. При створенні за допомогою new багатомірних масивів варто вказувати все розмірності масиву. Наприклад,
int *mattr = new int[10][10][10]; // припустимо
int *mattr1 = new int[10][ ][10]; // не можна
Розмірність може бути вираженням довільної складності. Операція new повертає покажчик на перший елемент масиву. Наприклад:
int i = 200;
char *ps = new char[i*2]; // ps указує на масив з 400 елементів типу char.
Приклад 1. Програма використовує оператор new для одержання покажчика на 100-байтний масив.
#include <iostream.h>
void main(void)
{
char *pointer = new char[100];
if (pointer != NULL)
cout << "Пам'ять успішно виділена" << endl;
else
cout << "Помилка виділення пам'яті" << endl;
}
Як видно, програма відразу перевіряє значення, привласнене оператором new змінній-покажчику. Якщо покажчик містить значення NULL, значить new не зміг виділити запитуваний обсяг пам'яті. Якщо ж покажчик містить не NULL, отже, new успішно виділив пам'ять і покажчик містить адресу початку блоку пам'яті.
Наступна програма виділяє щораз пам'ять для 10000 символів доти, поки оператор new не зможе більше виділити пам'ять із вільної пам'яті. Інакше кажучи, ця програма втримує виділену пам'ять, поки не використовує всю доступну вільну пам'ять. Якщо програма успішно виділяє пам'ять, вона сповіщає про це повідомленням. Якщо пам'ять більше не може бути виділена, програма виводить повідомлення про помилку й завершується.
#include <iostream.h>
void main(void)
{
char * pointer;
do
{
pointer = new char[10000];
if (pointer != NULL)
cout << "Виділено 10000 байт" << endl
else
cout << "Більше немає пам'яті" << endl;
} while (pointer != NULL);
}
Зауваження. Якщо ви роботаєте в середовищі MS-DOS, то, можливо, будете здивовані тим, що вільна пам'ять вичерпається після того, як програма виділить 64 Кбайт. Більшість працюючих в MS-DOS компіляторів C++ за замовчуванням використовують малу модель пам'яті, що забезпечує тільки 64 Кбaйт вільної пам'яті. Аналогічно, якщо ви використовуєте середовище MS-DOS, то найбільша область пам'яті, до якої можуть звернутися ваші програми, може бути обмежена 64Кбайт.
Пам'ять, виділена за допомогою операції new, буде зайнята доти, поки програміст явно її не звільнить. Для явного звільнення цієї пам'яті використовується операція delete, що застосовується до покажчика, що адресує динамічний об'єкт. Наприклад:
int *pi = new int; // пам'ять відведено
delete pi; // пам'ять звільнено.
Тут пам'ять, займана *pi, знову вертається у вільну пам'ять і згодом знову може бути виділена за допомогою new.
Для звільнення пам'яті відведених масиву необхідно вставляти пари порожніх квадратних дужок між delete і покажчиком :
int *parr = new int[100];
delete [ ] parr;.
Для звільнення пам'яті, відведених масиву об'єктів, що не є класами, можна використовувати оператор delete і без квадратних дужок:
delete parr; // аналогічно delete [ ] parr.
Операція delete повинна застосовуватися тільки до пам'яті, виділеної за допомогою операції new. Застосування цього оператора до пам'яті, виділеної за допомогою іншого оператора, приведе до помилки. Однак, застосування delete до нульового покажчика не вважається помилкою й просто ігнорується. Особливо небезпечно повторне застосування delete до того самого покажчика. Але якщо цей покажчиз дорівнює NULL? то помилки не буде.
Одна з найпоширеніших помилок при використанні динамічної пам'яті виникає тоді, коли два покажчики адресують ту саму область пам'яті, і після застосування delete до одного з них застосування delete до іншого веде до повторного звільнення вже звільненій пам'яті й зависанню програми.
int *pi = new int[100];
int *pi2 = pi; // pi і pi2 указують на ту саму пам'ять
delete pi; // звільнити цю пам'ять
...
delete pi2; // зависання - повторне звільнення пам'яті
Складність налагодження полягає в тому, що між двома звільненнями може бути багато операторів і вони можуть бути в різних функціях і в різних файлах. До покажчика на константний об'єкт операцію delete застосовувати не можна, тому що вона все-таки міняє значення об'єкта :
const int *pi = new int(1024);
delete pi; // помилка компіляції.
Вільна пам'ять може вичерпатися по ходу виконання програми. За замовчуванням операція new повертає 0, якщо немає достатньої кількості вільної пам'яті. Тому необхідно враховувати, що new може повернути 0.
int *ia = new int[size];
if (ia) {// щось робимо з ia }
else { } // помилка - не вистачило пам'яті.
Програміст може розташувати об'єкт у вільній пам'яті по певній адресі. Для цього викликається операція new у наступному виді:
new ( адреса-розташування) тип;
Тут "адреса-розташування" повинна бути покажчиком. Для того, щоб використовувати цей варіант операції new, повинен бути підключений заголовний файл new.h. Ця можливість дозволяє програмістові попередньо виділяти пам'ять, що пізніше буде містити об'єкти, створені за допомогою цієї форми операції new, що може виявитися більше надійним способом їхнього розміщення. Наприклад:
int size = 10000; // тут пам'ять для buf є
char *buf = new char[sizeof(int)*size]; // відводимо пам'ять
// ... а тут цієї пам'яті вже могло б не бути
int *pi = new (buf) int[size]; // не відводимо пам'ять, а розміщаємо об'єкти типу int
// в buf у кількості size/
Виділення динамічної пам'яті для багатовимірного масиву розміром n - рядків, m - стовбців,який складається з дійсних чисел/
float A=new float *[n];
for (int i=0 ;i<n ;i++ )
A[i]=new float [m];
Спочаику виділяється адреса в динамічній пам'яті під масив покажчиків на рядки, а далі виділяємо пам'ять для кожного рядка. Звільнення пам'яті для цього масиву буде матиме вигляд
for (int i=0 ;i<n ;i++ )
delete [] A[i];
delete [] A;