Распределение памяти при работе программы
Схема распределения памяти под программу показана на следующем рисунке:
Большие адреса Меньшие адреса | Стек |
Динамическая область памяти (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
Дата добавления: 2019-02-07; просмотров: 675;