Тарифы Услуги Сим-карты

Выделение памяти в си. Динамическое выделение памяти в си. Динамическое и статическое выделение памяти. Преимущества и недостатки. Выделение памяти для одиночных переменных операторами new и delete. Возможные критические ситуации при выделении памяти. Ин

Последнее обновление: 28.05.2017

При создании массива с фиксированными размерами под него выделяется определенная память. Например, пусть у нас будет массив с пятью элементами:

Double numbers = {1.0, 2.0, 3.0, 4.0, 5.0};

Для такого массива выделяется память 5 * 8 (размер типа double) = 40 байт. Таким образом, мы точно знаем, сколько в массиве элементов и сколько он занимает памяти. Однако это не всегда удобно. Иногда бывает необходимо, чтобы количество элементов и соответственно размер выделяемой памяти для массива определялись динамически в зависимости от некоторых условий. Например, пользователь сам может вводить размер массива. И в этом случае для создания массива мы можем использовать динамическое выделение памяти.

Для управления динамическим выделением памяти используется ряд функций, которые определены в заголовочном файле stdlib.h :

    malloc() . Имеет прототип

    Void *malloc(unsigned s);

    Выделяет память длиной в s байт и возвращает указатель на начало выделенной памяти. В случае неудачного выполнения возвращает NULL

    calloc() . Имеет прототип

    Void *calloc(unsigned n, unsigned m);

    Выделяет память для n элементов по m байт каждый и возвращает указатель на начало выделенной памяти. В случае неудачного выполнения возвращает NULL

    realloc() . Имеет прототип

    Void *realloc(void *bl, unsigned ns);

    Изменяет размер ранее выделенного блока памяти, на начало которого указывает указатель bl, до размера в ns байт. Если указатель bl имеет значение NULL , то есть память не выделялась, то действие функции аналогично действию malloc

    free() . Имеет прототип

    Void *free(void *bl);

    Освобождает ранее выделенный блок памяти, на начало которого указывает указатель bl.

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

Рассмотрим применение функций на простой задаче. Длина массива неизвестна и вводится во время выполнения программы пользователем, и также значения всех элементов вводятся пользователем:

#include #include int main(void) { int *block; // указатель для блока памяти int n; // число элементов массива // ввод числа элементов printf("Size of array="); scanf("%d", &n); // выделяем память для массива // функция malloc возвращает указатель типа void* // который автоматически преобразуется в тип int* block = malloc(n * sizeof(int)); // вводим числа в массив for(int i=0;i

Консольный вывод программы:

Size of array=5 block=23 block=-4 block=0 block=17 block=81 23 -4 0 17 81

Здесь для управления памятью для массива определен указатель block типа int . Количество элементов массива заранее неизвестно, оно представлено переменной n.

Вначале пользователь вводит количество элементов, которое попадает в переменную n. После этого необходимо выделить память для данного количества элементов. Для выделения памяти здесь мы могли бы воспользоваться любой из трех вышеописанных функций: malloc, calloc, realloc. Но конкретно в данной ситуации воспользуемся функцией malloc :

Block = malloc(n * sizeof(int));

Прежде всего надо отметить, что все три выше упомянутые функции для универсальности возвращаемого значения в качестве результата возвращают указатель типа void * . Но в нашем случае создается массив типа int, для управления которым используется указатель типа int * , поэтому выполняется неявное приведение результата функции malloc к типу int * .

В саму функцию malloc передается количество байтов для выделяемого блока. Это количество подсчитать довольно просто: достаточно умножить количество элементов на размер одного элемента n * sizeof(int) .

После выполнения всех действий память освобождается с помощью функции free() :

Free(block);

Важно, что после выполнения этой функции мы уже не сможем использовать массив, например, вывести его значения на консоль:

Free(block); for(int i=0;i

И если мы попытаемся это сделать, то получим неопределенные значения.

Вместо функции malloc аналогичным образом мы могли бы использовать функцию calloc() , которая принимает количество элементов и размер одного элемента:

Block = calloc(n, sizeof(int));

Либо также можно было бы использовать функцию realloc() :

Int *block = NULL; block = realloc (block, n * sizeof(int));

При использовании realloc желательно (в некоторых средах, например, в Visual Studio, обязательно) инициализировать указатель хотя бы значением NULL.

Но в целом все три вызова в данном случае имели бы аналогичное действие:

Block = malloc(n * sizeof(int)); block = calloc(n, sizeof(int)); block = realloc (block, n * sizeof(int));

Теперь рассмотрим более сложную задачу - динамическое выделение памяти для двухмерного массива:

#include #include int main(void) { int **table; // указатель для блока памяти для массива указателей int *rows; // указатель для блока памяти для хранения информации по строкам int rowscount; // количество строк int d; // вводимое число // ввод количества строк printf("Rows count="); scanf("%d", &rowscount); // выделяем память для двухмерного массива table = calloc(rowscount, sizeof(int*)); rows = malloc(sizeof(int)*rowscount); // цикл по строкам for (int i = 0; i

Переменная table представляет указатель на массив указателей типа int* . Каждый указатель table[i] в этом массиве представляет указатель на подмассив элементов типа int , то есть отдельные строки таблицы. А переменная table фактически представляет указатель на массив указателей на строки таблицы.

Для хранения количества элементов в каждом подмассиве определяется указатель rows типа int . Фактически он хранит количество столбцов для каждой строки таблицы.

Сначала вводится количество строк в переменную rowscount . Количество строк - это количество указателей в массиве, на который указывает указатель table . И кроме того, количество строк - это количество элементов в динамическом массиве, на который указывает указатель rows . Поэтому вначале необходимо для всех этих массивов выделить память:

Table = calloc(rowscount, sizeof(int*)); rows = malloc(sizeof(int)*rowscount);

Далее в цикле осуществляется ввод количества столбцов для каждый строки. Введенное значение попадает в массив rows. И в соответствии с введенным значением для каждой строки выделяется необходимый размер памяти:

Scanf("%d", &rows[i]); table[i] = calloc(rows[i], sizeof(int));

Затем производится ввод элементов для каждой строки.

В конце работы программы при выводе происходит освобождение памяти. В программе память выделяется для строк таблицы, поэтому эту память надо освободить:

Free(table[i]);

И кроме того, освобождается память, выделенная для указателей table и rows:

Free(table); free(rows);

Консольный вывод программы:

Rows count=2 Columns count for 1=3 table=1 table=2 table=3 Columns count for 2=2 table=4 table=5 1 2 3 4 5

Мы открыли для себя возможности динамического выделения памяти. Что это значит? Это значит то, что при динамическом выделении памяти, память резервируется не на этапе компиляции а на этапе выполнения программы. И это дает нам возможность выделять память более эффективно, в основном это касается массивов. С динамическим выделением память, нам нет необходимости заранее задавать размер массива, тем более, что не всегда известно, какой размер должен быть у массива. Далее рассмотрим каким же образом можно выделять память.

Выделение памяти в Си (функция malloc)

Функция malloc() определена в заголовочном файле stdlib.h , она используется для инициализации указателей необходимым объемом памяти. Память выделяется из сектора оперативной памяти доступного для любых программ, выполняемых на данной машине. Аргументом является количество байт памяти, которую необходимо выделить, возвращает функция — указатель на выделенный блок в памяти. Функция malloc() работает также как и любая другая функция, ничего нового.

Так как различные типы данных имеют разные требования к памяти, мы как-то должны научиться получить размер в байтах для данных разного типа. Например, нам нужен участок памяти под массив значений типа int — это один размер памяти, а если нам нужно выделить память под массив того же размера, но уже типа char — это другой размер. Поэтому нужно как-то вычислять размер памяти. Это может быть сделано с помощью операции sizeof() , которая принимает выражение и возвращает его размер. Например, sizeof(int) вернет количество байтов, необходимых для хранения значения типа int . Рассмотрим пример:

#include int *ptrVar = malloc(sizeof(int));

В этом примере, в строке 3 указателю ptrVar присваивается адрес на участок памяти, размер которого соответствует типу данных int . Автоматически, этот участок памяти становится недоступным для других программ. А это значит, что после того, как выделенная память станет ненужной, её нужно явно высвободить. Если же память не будет явно высвобождена, то по завершению работы программы, память так и не освободится для операционной системы, это называется утечкой памяти. Также можно определять размер выделяемой памяти, которую нужно выделить передавая пустой указатель, вот пример:

Int *ptrVar = malloc(sizeof(*ptrVar));

Что здесь происходит? Операция sizeof(*ptrVar) оценит размер участка памяти, на который ссылается указатель. Так как ptrVar является указателем на участок памяти типа int , то sizeof() вернет размер целого числа. То есть, по сути, по первой части определения указателя, вычисляется размер для второй части. Так зачем же это нам надо? Это может понадобиться, если вдруг необходимо поменять определение указателя, int , например, на float и тогда, нам не нужно менять тип данных в двух частях определения указателя. Достаточно будет того, что мы поменяем первую часть:

Float *ptrVar = malloc(sizeof(*ptrVar));

Как видите, в такой записи есть одна очень сильная сторона, мы не должны вызывать функцию malloc() с использованием sizeof(float) . Вместо этого мы передали в malloc() указатель на тип float , в таком случае, размер выделяемой памяти автоматически определится сам!

Особенно это пригодится, если выделять память потребуется далеко от определения указателя:

Float *ptrVar; /* . . . сто строк кода */ . . . ptrVar = malloc(sizeof(*ptrVar));

Если бы вы использовали конструкцию выделения памяти с операцией sizeof() , то вам бы пришлось находить в коде определение указателя, смотреть его тип данных и уже потом вы бы смогли правильно выделить память.

Высвобождение выделенной памяти

Высвобождение памяти выполняется с помощью функции free() . Вот пример:

Free(ptrVar);

После освобождения памяти, хорошей практикой является сброс указателя в нуль, то есть присвоить *ptrVar = 0 . Если указателю присвоить 0, указатель становится нулевым, другими словами, он уже никуда не указывает. Всегда после высвобождения памяти, присваивайте указателю 0, в противном случае, даже после высвобождения памяти, указатель все равно на неё указывает, а значит вы случайно можете нанести вред другим программам, которые, возможно будут использовать эту память, но вы даже ничего об этом не узнаете и будете думать, что программа работает корректно.

P.S.: Всем, кто увлекается видеомонтажом может быть интересен этот редактор видео Windows 7 . Видеоредактор называется Movavi, может кто-то уже с ним знаком или даже работал с ним. С помощью этой программы на русском языке, вы легко можете добавить видео с камеры, улучшить качество и наложить красивые видео эффекты.

В С++, как и во многих других языках, память можно выделять статически (память выделяется до начала выполнения программы и освобождается после завершения программы) или динамически (память выделяется и освобождается в процессе выполнения программы).

Статическое выделение памяти выполняется для всех глобальных и локальных переменных, имеющих явные описания в программе (без использования указателей). В этом случае механизм выделения памяти определяется расположением описания переменной в программе и спецификатором класса памяти в описании. Тип переменной определяет размер выделяемой области памяти, но механизм выделения памяти от типа не зависит. Имеется два основных механизма статического выделения памяти.

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

· Для локальных переменных, объявленных внутри какого-либо блока и не имеющих спецификатора static, память выделяется другим способом. До начала выполнения программы (при её загрузке) выделяется довольно объёмная область памяти, называемая стеком (иногда используют термины стек программы или стек вызовов , чтобы сделать различие между стеком как абстрактным типом данных). Размер стека зависит от среды разработки, например, в MS Visual C++ по умолчанию под стек выделяется 1 мегабайт (это значение поддаётся настройке). В процессе выполнения программы при входе в определённый блок выделяется память в стеке для локализованных в блоке переменных (в соответствии с описанием их типа), при выходе из блока эта память освобождается. Данные процессы выполняются автоматически, поэтому локальные переменные в С++ часто называют автоматическими .

При вызове функции в стеке выделяется память для её локальных переменных, параметров (в стек помещается значение или адрес параметра), результата функции и сохранения точки возврата – адреса в программе, куда нужно вернуться при завершении работы функции. При завершении работы функции все связанные с ней данные удаляются из стека.

Использование термина "стек" объяснить легко – при принятом подходе к выделению и освобождению памяти переменные, которые помещаются в стек последними (это переменные, локализованные в самом глубоко вложенном блоке), удаляются из него первыми. То есть, выделение и освобождение памяти происходит по принципу LIFO (LAST IN – FIRST OUT, последним пришёл – первым вышел). Это и есть принцип работы стека. Стек как динамическую структуру данных и его возможную реализацию мы рассмотрим в следующем разделе.



Во многих случаях статическое выделение памяти ведет к ее неэффективному использованию (особенно это характерно для массивов больших размеров), т. к. не всегда выделенная статически область памяти реально заполняется данными. Поэтому в С++, как и во многих языках, есть удобные средства динамического формирования переменных. Суть динамического выделения памяти заключается в том, что память выделяется (захватывается) по запросу из программы и освобождается также по запросу. При этом размер памяти может определяться типом переменной или явно указываться в запросе. Такие переменные называются динамическими . Возможности создания и использования динамических переменных тесно связаны с механизмом указателей.

Суммируя всё сказанное выше, можно представить следующую схему распределения памяти в процессе исполнения программы (рисунок 2.1). Расположение областей друг относительно друга на рисунке довольно условное, т.к. детали выделения памяти берёт на себя операционная система.

Рисунок 2.1 – схема распределения памяти

В заключение этого раздела коснёмся одной болезненной проблемы в процессе работы со стеком – возможности его переполнения (эта аварийная ситуация обычно называется Stack Overflow ). Причина, породившая проблему, понятна – ограниченный объём памяти, которая выделяется под стек при загрузке программы. Наиболее вероятные ситуации для переполнения стека – локальные массивы больших размеров и глубокая вложенность рекурсивных вызовов функций (обычно возникает при неаккуратном программировании рекурсивных функций, допустим, забыта какая-либо терминальная ветвь).



Для того, чтобы лучше понять проблему переполнения стека, советуем провести такой нехитрый эксперимент. В функции main объявите массив целых чисел размером, допустим, на миллион элементов. Программа скомпилируется, но при её запуске возникнет ошибка переполнения стека. Теперь добавьте в начало описания массива спецификатор static (или вынесите описание массива из функции main ) – программа заработает!

Ничего чудесного в этом нет – просто теперь массив помещается не в стек, а в область глобальных и статических переменных. Размер памяти для этой области определяет компилятор – если программа скомпилировалась, значит, она будет работать.

Тем не менее, объявлять в программе статически формируемые массивы огромных размеров, как правило, нет необходимости. В большинстве случаев более эффективным и гибким способом будет динамическое выделение памяти для таких данных.

Динамическое выделение памяти необходимо для эффективного использования памяти компьютера. Например, мы написали какую-то программку, которая обрабатывает массив. При написании данной программы необходимо было объявить массив, то есть задать ему фиксированный размер (к примеру, от 0 до 100 элементов). Тогда данная программа будет не универсальной, ведь может обрабатывать массив размером не более 100 элементов. А если нам понадобятся всего 20 элементов, но в памяти выделится место под 100 элементов, ведь объявление массива было статическим, а такое использование памяти крайне не эффективно.

В С++ операции new и delete предназначены для динамического распределения памяти компьютера. Операция new выделяет память из области свободной памяти, а операция delete высвобождает выделенную память. Выделяемая память, после её использования должна высвобождаться, поэтому операции new и delete используются парами. Даже если не высвобождать память явно, то она освободится ресурсами ОС по завершению работы программы. Рекомендую все-таки не забывать про операцию delete .

// пример использования операции new int *ptrvalue = new int; //где ptrvalue – указатель на выделенный участок памяти типа int //new – операция выделения свободной памяти под создаваемый объект.

Операция new создает объект заданного типа, выделяет ему память и возвращает указатель правильного типа на данный участок памяти. Если память невозможно выделить, например, в случае отсутствия свободных участков, то возвращается нулевой указатель, то есть указатель вернет значение 0. Выделение памяти возможно под любой тип данных: int , float ,double , char и т. д.

// пример использования операции delete: delete ptrvalue; // где ptrvalue – указатель на выделенный участок памяти типа int // delete – операция высвобождения памяти

Разработаем программу, в которой будет создаваться динамическая переменная.

// new_delete.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" #include << "ptrvalue = " << *ptrvalue << endl; delete ptrvalue; // высвобождение памяти system("pause"); return 0; }

// код Code::Blocks

// код Dev-C++

// new_delete.cpp: определяет точку входа для консольного приложения. #include using namespace std; int main(int argc, char* argv) { int *ptrvalue = new int; // динамическое выделение памяти под объект типа int *ptrvalue = 9; // инициализация объекта через указатель //int *ptrvalue = new int (9); инициализация может выполнятся сразу при объявлении динамического объекта cout << "ptrvalue = " << *ptrvalue << endl; delete ptrvalue; // высвобождение памяти return 0; }

В строке 10 показан способ объявления и инициализации девяткой динамического объекта, все, что нужно так это указать значение в круглых скобочках после типа данных. Результат работы программы показан на рисунке 1.

Ptrvalue = 9 Для продолжения нажмите любую клавишу. . .

Рисунок 1 — Динамическая переменная

Создание динамических массивов

Как было сказано раньше, массивы также могут быть динамическими. Чаще всего операции new и delete применяются для создания динамических массивов, а не для создания динамических переменных. Рассмотрим фрагмент кода создания одномерного динамического массива.

// объявление одномерного динамического массива на 10 элементов: float *ptrarray = new float ; // где ptrarray – указатель на выделенный участок памяти под массив вещественных чисел типа float // в квадратных скобочках указываем размер массива

После того как динамический массив стал ненужным, нужно освободить участок памяти, который под него выделялся.

// высвобождение памяти отводимой под одномерный динамический массив: delete ptrarray;

После оператора delete ставятся квадратные скобочки, которые говорят о том, что высвобождается участок памяти, отводимый под одномерный массив. Разработаем программу, в которой создадим одномерный динамический массив, заполненный случайными числами.

// new_delete_array.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" #include // в заголовочном файле // в заголовочном файле < 10; count++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 cout << "array = "; for (int count = 0; count < 10; count++) cout << setprecision(2) << ptrarray << " "; delete ptrarray; // высвобождение памяти cout << endl; system("pause"); return 0; }

// код Code::Blocks

// код Dev-C++

// new_delete_array.cpp: определяет точку входа для консольного приложения. #include // в заголовочном файле содержится прототип функции time() #include // в заголовочном файле содержится прототип функции setprecision() #include #include using namespace std; int main(int argc, char* argv) { srand(time(0)); // генерация случайных чисел float *ptrarray = new float ; // создание динамического массива вещественных чисел на десять элементов for (int count = 0; count < 10; count++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 cout << "array = "; for (int count = 0; count < 10; count++) cout << setprecision(2) << ptrarray << " "; delete ptrarray; // высвобождение памяти cout << endl; system("pause"); return 0; }

Созданный одномерный динамический массив заполняется случайными вещественными числами, полученными c помощью функций генерации случайных чисел, причём числа генерируются в интервале от 1 до 10, интервал задается так — rand() % 10 + 1 . Чтобы получить случайные вещественные числа, выполняется операция деления, с использованием явного приведения к вещественному типу знаменателя — float((rand() % 10 + 1)) . Чтобы показать только два знака после запятой используем функцию setprecision(2) , прототип данной функции находится в заголовочном файле . Функция time(0) засевает генератор случайных чисел временным значением, таким образом, получается, воспроизводить случайность возникновения чисел (см. Рисунок 2).

Array = 0.8 0.25 0.86 0.5 2.2 10 1.2 0.33 0.89 3.5 Для продолжения нажмите любую клавишу. . .

Рисунок 2 — Динамический массив в С++

По завершению работы с массивом, он удаляется, таким образом, высвобождается память, отводимая под его хранение.

Как создавать и работать с одномерными динамическими массивами мы научились. Теперь рассмотрим фрагмент кода, в котором показано, как объявляется двумерный динамический массив.

// объявление двумерного динамического массива на 10 элементов: float **ptrarray = new float* ; // две строки в массиве for (int count = 0; count < 2; count++) ptrarray = new float ; // и пять столбцов // где ptrarray – массив указателей на выделенный участок памяти под массив вещественных чисел типа float

Сначала объявляется указатель второго порядка float **ptrarray , который ссылается на массив указателей float* ,где размер массива равен двум. После чего в цикле for каждой строке массива объявленного в строке 2 выделяется память под пять элементов. В результате получается двумерный динамический массив ptrarray .Рассмотрим пример высвобождения памяти отводимой под двумерный динамический массив.

// высвобождение памяти отводимой под двумерный динамический массив: for (int count = 0; count < 2; count++) delete ptrarray; // где 2 – количество строк в массиве

Объявление и удаление двумерного динамического массива выполняется с помощью цикла, так как показано выше, необходимо понять и запомнить то, как это делается. Разработаем программу, в которой создадим двумерный динамический массив.

// new_delete_array2.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" #include #include #include < 2; count++) ptrarray = new float ; // и пять столбцов // заполнение массива for (int count_row = 0; count_row < 2; count_row++) for (int count_column = 0; count_column < 5; count_column++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 // вывод массива for (int count_row = 0; count_row < 2; count_row++) { for (int count_column = 0; count_column < 5; count_column++) cout << setw(4) <

// код Code::Blocks

// код Dev-C++

// new_delete_array2.cpp: определяет точку входа для консольного приложения. #include #include #include #include using namespace std; int main(int argc, char* argv) { srand(time(0)); // генерация случайных чисел // динамическое создание двумерного массива вещественных чисел на десять элементов float **ptrarray = new float* ; // две строки в массиве for (int count = 0; count < 2; count++) ptrarray = new float ; // и пять столбцов // заполнение массива for (int count_row = 0; count_row < 2; count_row++) for (int count_column = 0; count_column < 5; count_column++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 // вывод массива for (int count_row = 0; count_row < 2; count_row++) { for (int count_column = 0; count_column < 5; count_column++) cout << setw(4) <

При выводе массива была использована функция setw() , если вы не забыли, то она отводит место заданного размера под выводимые данные. В нашем случае, под каждый элемент массива по четыре позиции, это позволяет выровнять, по столбцам, числа разной длинны (см. Рисунок 3).

2.7 10 0.33 3 1.4 6 0.67 0.86 1.2 0.44 Для продолжения нажмите любую клавишу. . .

Рисунок 3 — Динамический массив в С++

время выполнения программы. Под локальные переменные программа отводит память из стекового пространства. Однако локальные переменные требуют предварительного определения объема памяти, выделяемой для каждой ситуации. Хотя С++ эффективно реализует такие переменные, они требуют от программиста заранее знать, какое количество памяти необходимо для каждой ситуации.

Второй способ, которым С++ может хранить информацию, заключается в использовании системы динамического распределения. При этом способе память распределяется для информации из свободной области памяти по мере необходимости. Область свободной памяти находится между кодом программы с ее постоянной областью памяти и стеком ( рис. 24.1). Динамическое размещение удобно, когда неизвестно, сколько элементов данных будет обрабатываться.


Рис. 24.1.

По мере использования программой стековая область увеличивается вниз, то есть программа сама определяет объем стековой памяти. Например, программа с большим числом рекурсивных функций займет больше стековой памяти, чем программа , не имеющая рекурсивных функций , так как локальные переменные и возвращаемые адреса хранятся в стеках. Память под саму программу и глобальные переменные выделяется на все время выполнения программы и является постоянной для конкретной среды.

Память , выделяемая в процессе выполнения программы, называется динамической. После выделения динамической памяти она сохраняется до ее явного освобождения, что может быть выполнено только с помощью специальной операции или библиотечной функции.

Если динамическая память не освобождена до окончания программы, то она освобождается автоматически при завершении программы. Тем не менее, явное освобождение ставшей ненужной памяти является признаком хорошего стиля программирования.

В процессе выполнения программы участок динамической памяти доступен везде, где доступен указатель , адресующий этот участок. Таким образом, возможны следующие три варианта работы с динамической памятью , выделяемой в некотором блоке (например, в теле неглавной функции).

  • Указатель (на участок динамической памяти) определен как локальный объект автоматической памяти. В этом случае выделенная память будет недоступна при выходе за пределы блока локализации указателя, и ее нужно освободить перед выходом из блока.
  • Указатель определен как локальный объект статической памяти. Динамическая память, выделенная однократно в блоке, доступна через указатель при каждом повторном входе в блок. Память нужно освободить только по окончании ее использования.
  • Указатель является глобальным объектом по отношению к блоку. Динамическая память доступна во всех блоках, где "виден" указатель. Память нужно освободить только по окончании ее использования.

Все переменные, объявленные в программе размещаются в одной непрерывной области памяти, которую называют сегментом данных . Такие переменные не меняют своего размера в ходе выполнения программы и называются статическими . Размера сегмента данных может быть недостаточно для размещения больших объемов информации. Выходом из этой ситуации является использование динамической памяти. Динамическая память – это память , выделяемая программе для ее работы за вычетом сегмента данных, стека, в котором размещаются локальные переменные подпрограмм и собственно тела программы.

Для работы с динамической памятью используют указатели. С их помощью осуществляется доступ к участкам динамической памяти, которые называются динамическими переменными . Для хранения динамических переменных выделяется специальная область памяти, называемая " кучей ".

Динамические переменные создаются с помощью специальных функций и операций. Они существуют либо до конца работы программы, либо до тех пор, пока не будет освобождена выделенная под них память с помощью специальных функций или операций. То есть время жизни динамических переменных – от точки создания до конца программы или до явного освобождения памяти .

В С++ используется два способа работы с динамической памятью:

  1. использование операций new и delete ;
  2. использование семейства функций mallос (calloc ) (унаследовано из С).

Работа с динамической памятью с помощью операций new и delete

В языке программирования С++ для динамического распределения памяти существуют операции new и delete . Эти операции используются для выделения и освобождения блоков памяти . Область памяти, в которой размещаются эти блоки, называется свободной памятью .

Операция new позволяет выделить и сделать доступным свободный участок в основной памяти, размеры которого соответствуют типу данных, определяемому именем типа.

Синтаксис :

new ИмяТипа;

new ИмяТипа [Инициализатор];

В выделенный участок заносится значение , определяемое инициализатором , который не является обязательным элементом. В случае успешного выполнения new возвращает адрес начала выделенного участка памяти. Если участок нужных размеров не может быть выделен (нет памяти), то операция new возвращает нулевое значение адреса (NULL ).

Синтаксис применения операции :

Указатель = new ИмяТипа [Инициализатор];

Операция new float выделяет участок памяти размером 4 байта. Операция new int(15) выделяет участок памяти 4 байта и инициализирует этот участок целым значением 15. Синтаксис использования операций new и delete предполагает применение указателей. Предварительно каждый указатель должен быть объявлен:

тип *ИмяУказателя;

Например:

float *pi; //Объявление переменной pi pi=new float; //Выделение памяти для переменной pi * pi = 2.25; //Присваивание значения

В качестве типа можно использовать, например, стандартные типы int, long, float, double, char .

Оператор new чаще всего используется для размещения в памяти данных определенных пользователем типов, например, структур:

struct Node { char *Name; int Value; Node *Next }; Node *PNode; //объявляется указатель PNode = new Node; //выделяется память PNode->Name = "Ata"; //присваиваются значения PNode->Value = 1; PNode->Next = NULL;

В качестве имени типа в операции new может быть использован массив :

new ТипМассива

При выделении динамической памяти для массива его размеры должны быть полностью определены. Например:

ptr = new int ;//10 элементов типа int или 40 байт ptr = new int ;//неверно, т.к. не определен размер

Такая операция позволяет выделить в динамической памяти участок для размещения массива соответствующего типа, но не позволяет его инициализировать. В результате выполнения операция new возвратит указатель , значением которого служит адрес первого элемента массива. Например:

int *n = new int;

Операция new выполняет выделение достаточного для размещения величины типа int участка динамической памяти и записывает адрес начала этого участка в переменную n . Память под саму переменную n (размера, достаточного для размещения указателя) выделяется на этапе компиляции.