Диначеская память

Язык С++ поддерживает два основных типа выделения памяти:

  • Статическое выделение памяти выполняется для статических и глобальных переменных. Память выделяется один раз (при запуске программы) и сохраняется на протяжении работы всей программы.
  • Динамическое выделение памяти.

Как статическое выделение памяти имеют два общих свойства:

  • Размер переменной/массива должен быть известен во время компиляции.
  • Выделение и освобождение памяти происходит автоматически (когда переменная создается/уничтожается).

В некоторых случааях этого достаточно. Но в большинстве мы заранее не можем сказать какой длины будет считанная строка, или какой длины мыссив нам нужно для наших задач. А еще большая проблема - это что хоть память нынче “дешевая”, но она все равно не бесконечна. Приведенный ниже пример просто не запустится (вероятно) у вас:

int main()
{
    double m[10000000] = {};  //80 Mb
}

Почему? Потому что память для большинства обычных переменных (включая фиксированные массивы) выделяется из специального резервуара памяти — стеке (heap, не нужно путать с одноименной стрктурой памяти). Объем памяти стека в программе, как правило, не большой. Если вы превысите это значение, то произойдет переполнение стека, и операционная система автоматически завершит выполнение вашей программы.

Все эти проблемы призвано решить динамическое выделение памяти. Перед тем как рассмотртеть выделение памяти в стиле C++ рассмотрим стиль C (чтобы понимать как стало проще жить).

Выделение памяти в стиле C

Стандартная библиотека cstdlib предоставляет четыре функции для управления памятью:

void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
void free(void *ptr);

где size_t - специальный целочисленный беззнаковый тип, может вместить в себя размер любого типа в байтах. Тип size_t используется для указания размеров типов данных, для индексации массивов и прочее. void* — это указатель на нетипизированную память.

  • malloc - функция выделяет область памяти размера size. Данные не инициализируются
  • callco - выделяет массив из nmemb размера size. Данные инициализируются нулём
  • realloc — изменяет размер области памяти по указателю ptr на size (если возможно, то это делается на месте).
  • free — освобождает область памяти, ранее выделенную одной из функций malloc/calloc/realloc. Мы всегда после выдление памяти должны ее освобождать

Пример использования:

// создание массива из 1000 int
int *m = (int *)malloc(1000 * sizeof(int));
m[10] = 10;
// изменение размера массива до 2000
m = (int *)realloc(m, 2000 * sizeof(int));
// освобождение массива
free(m);
// создание массива нулей
m = (int *)calloc(3000, sizeof(int));
free(m);
m = 0;

Выделение памяти в стиле C++

Язык C++ предоставляет два набора операторов для выделения памяти:

  • new и delete - для одиночных значений
  • new[] и delete[] - для массивов

Версия оператора delete должна соответствовать версии оператора new

// выделение памяти под один int со значением 5
int *m = new int(5);
delete m; // освобождение памяти
// создание массива значений типа int
m = new int[1000];
delete[] m; // освобождение памяти

Типичные проблемы при работе с памятью

Во-первых, надо признать, что и у выделения памяти динамически есть свои недостатки:

  • медленее. Выделеить памяти на стеке намного “дешевле” вделения для нее динамической памяти
  • фрагментация. При большом количестве выделения памяти, она (память) становится фрагментарной. \
  • “утечка” памати. Самая большая проблема.

    В С/C++ разоботчик ответственен за корретное управление (выделение/удаление) памятью.

// создание массива из 1000 int
int *m = new int[1000];
// создание массива из 2000 int
m = new int[2000]; // утечка памяти
// Не вызван delete [] m, утечка памяти

Примеры неправильного осводождения памяти

int *m1 = new int[1000];
delete m1; // должно быть delete [] m1

int *p = new int(0);
free(p); // совмещение функций C++ и C

int *q1 = (int *)malloc(sizeof(int));
free(q1);
free(q1); // двойное удаление

int *q2 = (int *)malloc(sizeof(int));
free(q2);
q2 = 0; // обнуляем указатель (или nullptr)
free(q2); // правильно работает для q2 = 0