C++. Динамическое и статическое выделение памяти. Преимущества и недостатки. Выделение памяти для одиночных переменных операторами 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 = nullptr;

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

if (pd!=nullptr)
{
    *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;
}


Связанные темы