Распределение памяти при работе программы
Схема распределения памяти под программу показана на следующем рисунке:
Область кода программы предназначена для хранения инструкций функций программы, обеспечивающих обработку данных. Данные в программе представляются переменными и константами. Для хранения глобальных данных (существуют в течение всего времени работы программы) предназначена область глобальных данных. Стек программы используется при вызове функций для передачи параметров и хранения локальных данных. Распределение памяти для хранения всех обычных переменных осуществляется компилятором, и адреса и объемы соответствующих участков памяти (в области глобальных данных) жестко закреплены за этими переменными на все время работы программы и изменено быть не может. Однако во многих задачах невозможно заранее предсказать, сколько места (количество переменных, объемы массивов и т.д.) потребуется для решения задачи – это так называемые задачи с неопределенной размерностью. Решить эту проблему можно лишь в том случае, если иметь механизм, позволяющий создавать новые объекты по мере возникновения необходимости в этих объектах или изменять объемы памяти, выделенные под эти объекты (например, объемы массивов). Между областью глобальных данных и стеком располагается так называемая динамическая область памяти, которую как раз и можно использовать в процессе работы программы для реализации механизма динамического управления памятью. Динамическое выделение и освобождение памяти в стиле 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
Воспользуйтесь поиском по сайту: ©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|