Урок 9: «Динамическая память»


Постепенно мы подбираемся с вами к более сложному. Этот урок будет логическим продолжением темы указателей в C++. В этом уроке мы научимся выделять нужное количество памяти под нужды переменной.

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

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

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

Стек – это специальная структура данных, предназначенная для быстрого доступа к данным.  Эту структуру  еще часто называют LIFO (Last In First Out)– последним пришел, первым ушел. Подробнее о стеке и других динамических структурах поговорим в других уроках.  Вам пока важно знать, что это такое. Стек представляет собой, как бы обойму, в которую вместо патрона загоняется очередная переменная. Не хочется вдаваться в технические подробности в этом уроке. Просто скажу что в силу своей природы стековая память работает гораздо быстрее, чем обычная.  Почему так происходит, расскажу в уроке, посвященном динамическим структурам памяти.

Стек многим хорош, но вот у него есть маленькая проблема – ограниченный объем памяти. Примитивные типы данных занимают мало памяти и поэтому помещение их в стек является логически правильным решением, ускоряющим работу программы. Однако в C++, равно как и в других компилируемых языках, существуют громоздкие типы данных типа массивов, структур и классов. Переменные этих типов могут занимать обширные области памяти, что может привести к переполнению стека и экстренному прекращению работы вашей программы.  Это очень неприятный момент в кодировании. Чтобы избежать подобной участи, была придумана концепция размещения таких громоздких типов данных в свободной области памяти, именуемой кучей. Фактически, куча – это вся ваша оперативная память. Гигабайт или 4 Гигабайта – роли не играет. Все это будет кучей.

Что дает куча? Практически огромный размер памяти, способный вместить в себя даже самый большой тип данных. Пока вам это может быть непонятно, так как мы до сих пор использовали всего лишь стековую память. Тем не менее, говорить, что переменная находится в куче не совсем верно. Да, она там лежит, но доступ к ней осуществляется посредством ссылки (читай, указателя), который хранится в стеке. А вот тут возникает интересная ситуация, о которой я все же расскажу.

Несмотря на то, что стеку  я посвящу отдельный урок, поясню как он работает. Я уже говорил, что это своеобразная обойма, в которую помещаются одна за другой переменные, как бы вталкиваемые в эту обойму (недаром эту операцию называют push). Вы уже применяли функции и наверняка использовали в них переменные. Вы не задумывались над тем, куда они девались после вызова функции? Правильно, они стирались. И происходило это благодаря механизму стека, в который помещались эти переменные. Как только они стали не нужны и программа осуществила выход из функции, указатель в стеке просто сдвинулся в низ, как бы отсекая область с переменными той функции. Графически я попытался изобразить это так:

 

В итоге часть стека просто стирается. Однако в куче все несколько иначе. Да, в стеке мы сотрем указатель на переменную, но сама переменная будет «жить» в куче. Такая переменная уже становится мусором. C++ не умеет собирать мусор, кладя этот сизифов труд на плечи программистов. Тем не менее, уже в середине этого курса мы с вами напишем сборщик мусора для C++.

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

int* dim=(int*)malloc (sizeof (int));
*dim=34;
cout<<*dim<<endl;
free (dim);

Как видите, мы вначале объявили динамическую переменную dim и выделили для нее память посредством malloc. Обратите внимание, что malloc и calloc возвращают тип void* (о нем мы поговорим в следующем уроке), поэтому мы приводим переменную к типу int*. Функция имеет единственный параметр – размер выделяемой памяти. Чтобы не ошибиться, я указал размер памяти через sizeof. Затем я присвоил динамической переменной значение 34. Теперь обратите внимание – динамическая переменная должна быть обязательно освобождена! Это делается при помощи функции free, параметром которой является наша переменная.

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

Тем не менее, сишный способ уже практически не актуален. Страупструп ввел новый стандарт языка, где используются  операторы new и delete. Сравните тот же код, только с этими операторами:

int* dim=new int;
*dim=34;
cout<<*dim<<endl;
delete dim;
dim=NULL; // явным образом очистим указатель от мусора.

Как видите, принцип практически тот же самый. Мы объявили динамическую переменную, выделили память для нее (причем компилятор сделал это сам за нас), и потом освободили память посредством delete. Затем я явным образом присвоил указателю dim значение NULL – пустой ссылки (в Паскале это nil). Это нужно для того, чтобы указатель не хранил адрес мусора.

Кстати, теперь  переменную dim можно использовать как простой указатель.  В общем, потренируйтесь.

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

<<Предыдущий урок                                                          Следующий урок>>

Обращение к читателям

Яндекс.Метрика