Динамічне та статичне виділення пам’яті. Переваги та недоліки. Виділення пам‘яті для одиночних змінних операторами new і delete. Можливі критичні ситуації при виділенні пам’яті. Ініціалізація при виділенні пам’яті

Динамічне та статичне виділення пам’яті. Переваги та недоліки. Виділення пам‘яті для одиночних змінних операторами new і delete. Можливі критичні ситуації при виділенні пам’яті. Ініціалізація при виділенні пам’яті


Зміст


1. Динамічне та статичне (фіксоване) виділення пам‘яті. Основні відмінності

Для роботи з масивами інформації програми мають виділяти пам‘ять для цих масивів. Для виділення пам‘яті під масиви змінних використовуються відповідні оператори, функції тощо. У мові програмування C++ виділяють такі види виділення пам‘яті:

1. Статичне (фіксоване) виділення пам‘яті. У цьому випадку пам‘ять виділяється тільки один раз під час компіляції. Розмір виділеної пам‘яті є фіксований і незмінний до кінця виконання програми. Прикладом такого виділення може служити оголошення масиву з 10 цілих чисел:

int M[10]; // пам‘ять для масиву виділяється один раз, розмір пам‘яті фіксований

2. Динамічне виділення пам‘яті. У цьому випадку використовується комбінація операторів new і delete. Оператор new виділяє пам‘ять для змінної (масиву) в спеціальній області пам‘яті, яка називається “купа” (heap). Оператор delete звільняє виділену пам‘ять. Кожному оператору new має відповідати свій оператор delete.

2. Переваги та недоліки використання динамічного та статичного способів виділення пам‘яті

Динамічне виділення пам‘яті порівняно зі статичним виділенням пам‘яті дає такі переваги:

  • пам‘ять виділяється в міру необхідності програмним шляхом;
  • немає зайвих витрат невикористаної пам‘яті. Виділяється стільки пам‘яті скільки потрібно і коли потрібно;
  • можна виділяти пам‘ять для масивів інформації, розмір яких наперед невідомий. Визначення розміру масиву формується у процесі виконання програми;
  • зручно здійснювати перерозподіл пам‘яті. Або іншими словами, зручно виділяти новий фрагмент для того самого масиву, якщо потрібно виділити додаткову пам‘ять або звільнити непотрібну;
  • при статичному способі виділення пам‘яті важко перерозподіляти пам‘ять для змінної-масиву, оскільки вона вже виділена фіксовано. У випадку динамічного виділення, це здійснюється легко і зручно.

Переваги статичного способу виділення пам‘яті:

  • статичне (фіксоване) виділення пам‘яті краще використовувати, коли розмір масиву інформації наперед відомий і є незмінний протягом виконання усієї програми;
  • статичне виділення пам‘яті не потребує додаткових операцій звільнення з допомогою оператора delete. Звідси випливає зменшення помилок програмування. Кожному оператору new має відповідати оператор delete;
  • природність (натуральність) представлення програмного коду, що оперує статичними масивами.

В залежності від поставленої задачі, програміст має вміти правильно визначити, який спосіб виділення пам‘яті підходить для тієї чи іншої змінної (масиву).

3. Як виділити пам‘ять оператором new для одиночної змінної? Загальна форма

Загальна форма виділення пам‘яті для одиночної змінної оператором new має вигляд:

ptrName = new type;

де

  • ptrName – ім‘я змінної (покажчика), яка буде вказувати на виділену пам‘ять;
  • type – тип змінної. Розмір пам‘яті виділяється достатній для поміщення в неї значення змінної даного типу type.

4. Як звільнити пам‘ять, виділену під одиночну змінну оператором delete? Загальна форма

Якщо пам‘ять для змінної виділена оператором new, то після завершення використання змінної, цю пам‘ять потрібно звільнити оператором delete. У мові C++ це є обов‘язковою умовою. Якщо не звільнити пам‘ять, то пам‘ять залишиться виділеною (зайнятою), але використовувати її не зможе жодна програма. У даному випадку відбудеться “витік пам‘яті” (memory leak).

У мовах програмування Java, C# звільняти пам‘ять після виділення не потрібно. Цим займається “збирач сміття” (garbage collector).

Загальна форма оператора delete для одиночної змінної:

delete ptrName;

де ptrName – ім‘я покажчика, для якого було раніше виділено пам‘ять оператором new. Після виконання оператора delete покажчик ptrName вказує на довільний фрагмент пам‘яті, який не є зарезервований (виділений).

5. Приклади виділення (new) та звільнення (delete) пам‘яті для покажчиків базових типів

У прикладах демонструється використання операторів new і delete. Приклади мають спрощений вигляд.

Приклад 1. Покажчик на тип int. Найпростіший приклад

// виділення пам'яті оператором new
int * p; // покажчик на int

p = new int; // виділити пам'ять для покажчика
*p = 25; // занести значення в пам'ять

// використання пам'яті, виділеної для покажчика
int d;

d = *p; // d = 25

// знищити пам'ять, виділену для покажчика - обов'язково
delete p;

Приклад 2. Покажчик на тип double

// виділення пам'яті для покажчика на double
double * pd = NULL;

pd = new double; // виділити пам'ять

if (pd != NULL)
{
    *pd = 10.89; // занести значення
    double d = *pd; // d = 10.89 - використання у програмі
    delete pd; // звільнити пам'ять
}

6. Що таке “витік пам‘яті” (memory leak)?

Витік пам‘яті” – це коли пам‘ять для змінної виділяється оператором new, а по закінченні роботи програми її не звільнено оператором delete. У цьому випадку пам‘ять в системі залишається зайнятою, хоча потреби в її використанні вже немає, оскільки програма, що її використовувала, вже давно завершила свою роботу.

“Витік пам‘яті” є типовою помилкою програміста. Якщо “витік пам‘яті” повторюється багатократно, то можлива ситуація, коли буде “зайнята” вся доступна пам‘ять в комп‘ютері. Це призведе до непередбачуваних наслідків роботи операційної системи.



7. Яким чином виділити пам‘ять оператором new з перехопленням критичної ситуації, при який пам‘ять може не виділитись? Виключна ситуація bad_alloc. Приклад

При використанні оператора new можлива ситуація, коли пам‘ять не виділиться. Пам‘ять може не виділитись у таких ситуаціях:

  • якщо відсутня вільна пам‘ять;
  • розмір вільної пам‘яті менши за той, що був заданий в операторі new.

У цьому випадку генерується виключна ситуація bad_alloc. Програма має перехватити цю ситуацію та відповідним чином обробити її.

Приклад. У прикладі враховується ситуація, коли пам‘ять може не виділитись оператором new. У такому випадку здійснюється спроба виділити пам’ять. Якщо спроба вдала, то робота програми продовжується. Якщо спроба завершилася невдачею, то відбувається вихід з функції з кодом -1.

int main()
{
    // оголосити масив покажчиків на float
    float * ptrArray;

    try
    {
        // спробувати виділити пам'ять для 10 елементів типу float
        ptrArray = new float[10];
    }
    catch (bad_alloc ba)
    {
        cout << "Виключна ситуація. Пам\'ять не виділена" << endl;
        cout << ba.what() << endl;
        return -1; // вихід з функції
    }

    // якщо все добре, то використання масиву
    for (int i = 0; i < 10; i++)
        ptrArray[i] = i * i + 3;

    int d = ptrArray[5];
    cout << d << endl;

    delete[] ptrArray; // знищити пам'ять, виділену під масив
    return 0;
}

8. Виділення пам‘яті для змінної з одночасною ініціалізацією. Загальна форма. Приклад

Оператор виділення пам‘яті new для одиночної змінної допускає одночасну ініціалізацію значенням цієї змінної.

В загальному, виділення пам‘яті для змінної з одночасною ініціалізацією має вигляд

ptrName = new type(value)

де

  • ptrName – ім‘я змінної-покажчика, для якої виділяється пам‘ять;
  • type – тип на який вказує покажчик ptrName;
  • value – значення, яке встановлюється для виділеної ділянки пам’яті (значення за покажчиком).

Приклад. Виділення пам’яті для змінних з одночасною ініціалізацією. Нижче наводиться функція main() для консольного додатку. Продемонстровано виділення пам’яті з одночасною ініціалізацією. Також враховується ситуація, якщо спроба виділити пам’ять завершується невдачею (критична ситуація bad_alloc).

#include "stdafx.h"
#include <iostream>
using namespace std;

int main()
{
    // виділення пам'яті з одночасною ініціалізацією
    float * pF;
    int * pI;
    char * pC;

    try
    {
        // спробувати виділити пам'ять для змінних з одночасною ініціалізацією
        pF = new float(3.88); // *pF = 3.88
        pI = new int(250); // *pI = 250
        pC = new char('M'); // *pC = 'M'
    }
    catch (bad_alloc ba)
    {
        cout << "Виключна ситуація. Пам\'ять не виділена" << endl;
        cout << ba.what() << endl;
        return -1; // вихід з функції
    }

    // якщо пам'ять виділена, то використання покажчиків pF, pI, pC
    float f = *pF; // f = 3.88
    int i = *pI; // i = 250;
    char c;
    c = *pC; // c = 'M'

    // вивести ініціалізовані значення
    cout << "*pF = " << f<< endl;
    cout << "*pI = " << i << endl;
    cout << "*pC = " << c << endl;

    // звільнити пам'ять, виділену раніше для покажчиків
    delete pF;
    delete pI;
    delete pC;

    return 0;
}


Зв’язані теми