double arrow

Распределение памяти при работе программы


Работа с динамической памятью

Схема распределения памяти под программу показана на следующем рисунке:

  Большие адреса Меньшие адреса Стек
Динамическая область памяти (heap - куча)
Область глобальных данных
Код программы

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

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

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




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

Динамическое выделение и освобождение памяти в стиле C++

Для динамического управления памятью в языке C++ используются две инструкции new и delete. Формат этих инструкций:

<Переменная-указатель> = new <Тип данных переменной-указателя>

delete <Переменная-указатель>

Инструкция newвыделяет в динамической области участок памяти, достаточный для размещения данных, тип которых определяется типом данных переменной-указателя, и возвращает адрес этого участка. Этот адрес присваивается переменной-указателю. Например:

double *p; //Переменная-указатель на типdouble

p = new double; //Выделение памяти

или так:

double *p = new double;

В этом примере инструкция newвыделяет в динамической области участок памяти объемом sizeof (double) и присваивает адрес этого участка переменной-указателю p. Дальнейшая работа с переменной-указателем осуществляется как с обычным указателем на тип данных double. Например:

*p = 3.14;

cout << *p * 2 << endl; // На экран выведено значение 6.28

cin >> *p;// Вводим с клавиатуры некоторое вещественное значение



cout << *p << endl; // На экран выведено значение, введенное с клавиатуры

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

delete p;

Инструкция delete возвращает участок памяти по адресу p в список свободной памяти, и в дальнейшем этот участок памяти может быть использован повторно для динамического размещения других данных.

Замечание. Инструкции new и delete это парные инструкции, то есть они всегда должны использоваться совместно – каждой инструкции newдолжна соответствовать инструкция delete. Динамическая область памяти автоматически освобождается только при завершении программы, поэтому неконтролируемое использование инструкции new может привести к переполнению динамической области памяти и, следовательно, к ошибкам в работе программы.

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

struct t_Person //Тип данных для "персоны"

{

char Fam[20];// Фамилия

char Name[20];// Имя

int Year;// Год рождения

};

setlocale ( 0, "" );// Русификация консоли

t_Person *p = new t_Person;// Создаем структуру в динамической памяти



strcpy ( (*p).Fam, "Иванов" );// Заносим фамилию

strcpy ( (*p).Name, "Иван" );// Заносим имя

(*p).Year = 1995;// Заносим год рождения

cout << "Фамилия: " << (*p).Fam << endl;// Выводим фамилию

cout << "Имя: " << (*p). Name << endl;// Выводим имя

cout << "Год рождения: " << (*p).Year << endl;// Выводим год рождения

delete p;// Освобождаем память

Для обращения к отдельным полям структуры через переменную-указатель мы использовали следующие конструкции:

(*p).Fam, (*p).Name, (*p).Year

Здесь (*p)обеспечивает разыменование указателя (получение данных персоны, расположенных в памяти по адресу p), а затем с помощью оператора “точка” осуществляется обращение к данным соответствующего поля.

Существует другой способ доступа к полям структур через указатель на структуру с помощью оператора “стрелка” (не требующий предварительного разыменования указателя). Это делается так:

p -> Fam, p -> Name, p -> Year

То есть следующий вариант той же программы будет также корректным:

struct t_Person //Тип данных для "персоны"

{

char Fam[20];// Фамилия

char Name[20];// Имя

int Year;// Год рождения

};

setlocale ( 0, "" );// Русификация консоли

t_Person *p = new t_Person;// Создаем структуру в динамической памяти

strcpy (p -> Fam, "Иванов" );// Заносим фамилию

strcpy (p -> Name, "Иван" );// Заносим имя

p -> Year = 1995;// Заносим год рождения

cout << "Фамилия: " << p -> Fam << endl;// Выводим фамилию

cout << "Имя: " << p -> Name << endl;// Выводим имя

cout << "Год рождения: " << p -> Year << endl;// Выводим год рождения

delete p;// Освобождаем память

Для некоторых типов данных одновременно с динамическим выделением памяти можно осуществлять и ее инициализацию. Например:

double *p = new double (3.14); // Инициализация значением 3.14

cout << *p << endl;// На экран выведено значение 3.14







Сейчас читают про: