Статическое выделение памяти в c
Аннотация: В лекции рассматриваются определения, распределение, способы выделения и освобождения динамической памяти, взаимодействие указателей и участков динамической памяти.
Цель лекции: изучить способы выделения памяти, динамического выделения памяти , связи указателей и динамического распределения памяти , научиться решать задачи с использованием динамического выделения памяти в языке C++.
Существует два основных способа хранения информации в оперативной памяти. Первый заключается в использовании глобальных и локальных переменных. В случае глобальных переменных выделяемые под них поля памяти остаются неизменными на все время выполнения программы. Под локальные переменные программа отводит память из стекового пространства. Однако локальные переменные требуют предварительного определения объема памяти, выделяемой для каждой ситуации. Хотя С++ эффективно реализует такие переменные, они требуют от программиста заранее знать, какое количество памяти необходимо для каждой ситуации.
Второй способ, которым С++ может хранить информацию, заключается в использовании системы динамического распределения. При этом способе память распределяется для информации из свободной области памяти по мере необходимости. Область свободной памяти находится между кодом программы с ее постоянной областью памяти и стеком ( рис. 24.1). Динамическое размещение удобно, когда неизвестно, сколько элементов данных будет обрабатываться.
По мере использования программой стековая область увеличивается вниз, то есть программа сама определяет объем стековой памяти. Например, программа с большим числом рекурсивных функций займет больше стековой памяти, чем программа , не имеющая рекурсивных функций , так как локальные переменные и возвращаемые адреса хранятся в стеках. Память под саму программу и глобальные переменные выделяется на все время выполнения программы и является постоянной для конкретной среды.
Память , выделяемая в процессе выполнения программы, называется динамической. После выделения динамической памяти она сохраняется до ее явного освобождения, что может быть выполнено только с помощью специальной операции или библиотечной функции.
Если динамическая память не освобождена до окончания программы, то она освобождается автоматически при завершении программы. Тем не менее, явное освобождение ставшей ненужной памяти является признаком хорошего стиля программирования.
В процессе выполнения программы участок динамической памяти доступен везде, где доступен указатель , адресующий этот участок. Таким образом, возможны следующие три варианта работы с динамической памятью, выделяемой в некотором блоке (например, в теле неглавной функции).
- Указатель (на участок динамической памяти) определен как локальный объект автоматической памяти. В этом случае выделенная память будет недоступна при выходе за пределы блока локализации указателя, и ее нужно освободить перед выходом из блока.
- Указатель определен как локальный объект статической памяти. Динамическая память, выделенная однократно в блоке, доступна через указатель при каждом повторном входе в блок. Память нужно освободить только по окончании ее использования.
- Указатель является глобальным объектом по отношению к блоку. Динамическая память доступна во всех блоках, где "виден" указатель. Память нужно освободить только по окончании ее использования.
Все переменные, объявленные в программе размещаются в одной непрерывной области памяти, которую называют сегментом данных. Такие переменные не меняют своего размера в ходе выполнения программы и называются статическими. Размера сегмента данных может быть недостаточно для размещения больших объемов информации. Выходом из этой ситуации является использование динамической памяти. Динамическая память – это память , выделяемая программе для ее работы за вычетом сегмента данных, стека, в котором размещаются локальные переменные подпрограмм и собственно тела программы.
Для работы с динамической памятью используют указатели. С их помощью осуществляется доступ к участкам динамической памяти, которые называются динамическими переменными. Для хранения динамических переменных выделяется специальная область памяти, называемая " кучей ".
Динамические переменные создаются с помощью специальных функций и операций. Они существуют либо до конца работы программы, либо до тех пор, пока не будет освобождена выделенная под них память с помощью специальных функций или операций. То есть время жизни динамических переменных – от точки создания до конца программы или до явного освобождения памяти .
В С++ используется два способа работы с динамической памятью:
- использование операций new и delete ;
- использование семейства функций mallос ( calloc ) (унаследовано из С).
Операторы new и delete[] . Выделение памяти для структурных переменных, объектов классов, массивов. Инициализация выделенной памяти. Пример перераспределения ранее выделенной памяти
Динамическое выделение памяти
Третий способ выделения памяти в языке Си++ – динамический . Память для величины какого-либо типа можно выделить, выполнив операцию new . В качестве операнда выступает название типа, а результатом является адрес выделенной памяти.
Созданный таким образом объект существует до тех пор, пока память не будет явно освобождена с помощью операции delete . В качестве операнда delete должен быть задан адрес , возвращенный операцией new :
Динамическое распределение памяти используется, прежде всего, тогда, когда заранее неизвестно, сколько объектов понадобится в программе и понадобятся ли они вообще. С помощью динамического распределения памяти можно гибко управлять временем жизни объектов, например выделить память не в самом начале программы (как для глобальных переменных), но, тем не менее, сохранять нужные данные в этой памяти до конца программы.
Если необходимо динамически создать массив , то нужно использовать немного другую форму new :
В отличие от определения переменной типа массив , размер массива в операции new может быть произвольным, в том числе вычисляемым в ходе выполнения программы. (Напомним, что при объявлении переменной типа массив размер массива должен быть константой.)
Освобождение памяти , выделенной под массив , должно быть выполнено с помощью следующей операции delete
Работа с динамической памятью с помощью операций new и delete
В языке программирования С++ для динамического распределения памяти существуют операции new и delete . Эти операции используются для выделения и освобождения блоков памяти . Область памяти, в которой размещаются эти блоки, называется свободной памятью.
Операция new позволяет выделить и сделать доступным свободный участок в основной памяти, размеры которого соответствуют типу данных, определяемому именем типа.
Статические переменные
Другой способ выделения памяти – статический
Если переменная определена вне функции, память для нее отводится статически, один раз в начале выполнения программы, и переменная уничтожается только тогда, когда выполнение программы завершается. Можно статически выделить память и под переменную, определенную внутри функции или блока. Для этого нужно использовать ключевое слово static в его определении:
В данном примере переменная visited задается в начале выполнения программы. Ее начальное значение – false . При первом вызове функции func условие в операторе if будет истинным, выполнится инициализация , и переменной visited будет присвоено значение true . Поскольку статическая переменная создается только один раз, ее значения между вызовами функции сохраняются. При втором и последующих вызовах функции func инициализация производиться не будет.
Если бы переменная visited не была объявлена static , то инициализация происходила бы при каждом вызове функции.
Работа с динамической памятью с помощью операций new и delete
В языке программирования С++ для динамического распределения памяти существуют операции new и delete . Эти операции используются для выделения и освобождения блоков памяти . Область памяти, в которой размещаются эти блоки, называется свободной памятью.
Операция new позволяет выделить и сделать доступным свободный участок в основной памяти, размеры которого соответствуют типу данных, определяемому именем типа.
Содержание
- 1. Динамическое и статическое (фиксированное) выделение памяти. Главные различия
- 2. Преимущества и недостатки использования динамического и статического способов выделения памяти
- 3. Как выделить память оператором new для одиночной переменной? Общая форма.
- 4. Как освободить память, выделенную под одиночную переменную оператором delete ? Общая форма
- 5. Примеры выделения ( new ) и освобождения ( delete ) памяти для указателей базовых типов
- 6. Что такое «утечка памяти» (memory leak)?
- 7. Каким образом выделить память оператором new с перехватом критической ситуации, при которой память может не выделиться? Исключительная ситуацияbad_alloc . Пример
- 8. Выделение памяти для переменной с одновременной инициализацией. Общая форма. Пример
Поиск на других ресурсах:
1. Динамическое и статическое (фиксированное) выделение памяти. Главные различия
Для работы с массивами информации, программы должны выделять память для этих массивов. Для выделения памяти под массивы переменных используются соответствующие операторы, функции и т.п.. В языке программирования C++ выделяют следующие способы выделения памяти:
1. Статическое (фиксированное) выделение памяти. В этом случае память выделяется только один раз во время компиляции. Размер выделенной памяти есть фиксированным и неизменным до конца выполнения программы. Примером такого выделения может служить объявление массива из 10 целых чисел:
2. Динамическое выделение памяти. В этом случае используется комбинация операторов new и delete . Оператор new выделяет память для переменной (массива) в специальной области памяти, которая называется «куча» (heap). Оператор delete освобождает выделенную память. Каждому оператору new должен соответствовать свой оператор delete .
2. Преимущества и недостатки использования динамического и статического способов выделения памяти
Динамическое выделение памяти по сравнению со статическим выделением памяти дает следующие преимущества:
- память выделяется по мере необходимости программным путем;
- нет лишних затрат неиспользованной памяти. Выделяется столько памяти сколько нужно и если нужно;
- можно выделять память для массивов информации, размер которых заведомо неизвестен. Определение размера массива формируется в процессе выполнения программы;
- удобно осуществлять перераспределение памяти. Или другими словами, удобно выделять новый фрагмент для одного и того же массива, если нужно выделить дополнительную память или освободить ненужную;
- при статическом способе выделения памяти трудно перераспределять память для переменной-массива, поскольку она уже выделена фиксировано. В случае динамического способа выделения, это делается просто и удобно.
Преимущества статического способа выделения памяти:
- статическое (фиксированное) выделение памяти лучше использовать, когда размер массива информации заведомо известен и есть неизменным на протяжении выполнения всей программы;
- статическое выделение памяти не требует дополнительных операций освобождения с помощью оператора delete . Отсюда вытекает уменьшение ошибок программирования. Каждому оператору new должен соответствовать свой оператор delete ;
- естественность (натуральность) представления программного кода, который оперирует статическими массивами.
В зависимости от поставленной задачи, программист должен уметь правильно определить, какой способ выделения памяти подходит для той или другой переменной (массива).
3. Как выделить память оператором new для одиночной переменной? Общая форма.
Общая форма выделения памяти для одиночной переменной оператором new имеет следующий вид:
- ptrName – имя переменной (указателя), которая будет указывать на выделенную память;
- type – тип переменной. Размер памяти выделяется достаточный для помещения в нее значения переменной данного типа type .
4. Как освободить память, выделенную под одиночную переменную оператором delete ? Общая форма
Если память для переменной выделена оператором new, то после завершения использования переменной, эту память нужно освободить оператором delete . В языке C++ это есть обязательным условием. Если не освободить память, то память останется выделенной (занятой), но использовать ее не сможет ни одна программа. В данном случае произойдет «утечка памяти» (memory leak).
Общая форма оператора delete для одиночной переменной:
где ptrName – имя указателя, для которого была раньше выделена память оператором new . После выполнения оператора delete указатель ptrName указывает на произвольный участок памяти, который не является зарезервированным (выделенным).
5. Примеры выделения ( new ) и освобождения ( delete ) памяти для указателей базовых типов
В примерах демонстрируется использование операторов new и delete . Примеры имеют упрощенный вид.
Пример 1. Указатель на тип int . Простейший пример
Пример 2. Указатель на тип double
⇑
6. Что такое «утечка памяти» ( memory leak )?
«Утечка памяти» – это когда память для переменной выделяется оператором new , а по окончании работы программы она не освобождается оператором delete . В этом случае память в системе остается занятой, хотя потребности в ее использовании уже нет, поскольку программа, которая ее использовала, уже давно завершила свою работу.
«Утечка памяти» есть типичной ошибкой программиста. Если «утечка памяти» повторяется многократно, то возможная ситуация, когда будет «занята» вся доступная память в компьютере. Это приведет к непредсказуемым последствиям работы операционной системы.
7. Каким образом выделить память оператором new с перехватом критической ситуации, при которой память может не выделиться? Исключительная ситуация bad_alloc . Пример
При использовании оператора new возможна ситуация, когда память не выделится. Память может не выделиться в следующих ситуациях:
- если отсутствует свободная память;
- размер свободной памяти меньше чем тот, который был задан в операторе new .
В этом случае генерируется исключительная ситуация bad_alloc . Программа может перехватить эту ситуацию и соответствующим образом обработать ее.
Пример. В примере учитывается ситуация, когда память может не выделиться оператором new . В таком случае осуществляется попытка выделить память. Если попытка удачная, то работа программы продолжается. Если попытка завершилась неудачей, то происходит выход из функции с кодом -1.
8. Выделение памяти для переменной с одновременной инициализацией. Общая форма. Пример
Оператор выделения памяти new для одиночной переменной допускает одновременную инициализацию значением этой переменной.
В общем, выделение памяти для переменной с одновременной инициализацией имеет вид
- ptrName – имя переменной-указателя, для которой выделяется память;
- type – тип на который указывает указатель ptrName ;
- value – значение, которое устанавливается для выделенного участка памяти (значение по указателю).
Пример. Выделение памяти для переменных с одновременной инициализацией. Ниже приводится функция main() для консольного приложения. Продемонстрировано выделение памяти с одновременной инициализацией. Также учитывается ситуация, когда попытка выделить память завершается неудачей (критическая ситуация bad_alloc ).
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Open with Desktop
- View raw
- Copy raw contents Copy raw contents
Copy raw contents
Copy raw contents
Кеш, оперативная память, стек и куча, выделение и освобождение памяти
Линейное представление памяти
Адрес | Значение (1 байт) |
---|---|
0x0000 | . |
. | . |
0x1000 | 1 |
0x1001 | 2 |
0x1002 | 3 |
0x1003 | 4 |
. | . |
0xffffffffff | . |
C-cast использовать в С++ нельзя! Как надо приводить типы в С++ и надо ли вообще будет в другой лекции
Знаковые | Беззнаковые |
---|---|
char | unsigned char |
short | unsigned short |
int | unsigned или unsigned int |
long | unsigned long |
Стандарт не регламентирует размер типов
Размер, бит | Тип |
---|---|
8 | int8_t, int_fast8_t, int_least8_t |
16 | int16_t, int_fast16_t, int_least16_t |
32 | int32_t, int_fast32_t, int_least32_t |
64 | int64_t, int_fast64_t, int_least64_t |
Беззнаковая (unsigned) версия - добавление префикса u
Память разбита на сегменты:
- кода (CS)
- данных (DS)
- стека (SS)
Регистр сегмента (CS, DS, SS) указывают на дескриптор.
Для инструкций и стека на смещение в сегменте указывает регистр:
Линейный адрес - это сумма базового адреса сегмента и смещения.
Segment limit (20 bit) - размер сегмента, 55-й бит G определяет гранулярность размера:
- байты, если 0
- страницы, если 1 (размер страницы обычно 4Кб)
- 000 - сегмент данных, только чтение
- 001 - сегмент данных, чтение и запись
- 010 - сегмент стека, только чтение
- 011 - сегмент стека, чтение и запись
- 100 - сегмент кода, только выполнение
- 101- сегмент кода, чтение и выполнение
Кроме задержки (latency) есть понятие пропускной способности (throughput, bandwidth). В случае чтения из RAM - 10-50 Gb/sec
Выводы из таблицы
- Стараться укладывать данные в кеш
- Минимизировать скачки по памяти
- В условиях основной веткой делать ветку которая выполняется с большей вероятностью
Классы управления памятью и областью видимости в C++
Характеризуются тремя понятиями:
Продолжительность хранения данных в памяти
Части кода из которых можно получить доступ к данным
Если к данным можно обратиться из другой единицы трансляции — связывание внешнее (external), иначе связывание внутреннее (internal)
Время жизни | Область видимости | Связывание |
---|---|---|
Автоматическое (блок) | Блок | Отсутствует |
Статический без связывания
Время жизни | Область видимости | Связывание |
---|---|---|
Статическое | Блок | Отсутствует |
Инициализируется при первом обращении
Статический с внутренним связыванием
Время жизни | Область видимости | Связывание |
---|---|---|
Статическое | Файл | Внутреннее |
Инициализируется до входа в main
Статический с внешним связыванием
Время жизни | Область видимости | Связывание |
---|---|---|
Статическое | Файл | Внешнее |
Выделение памяти на стеке очень быстрая, но стек не резиновый
Память в куче выделяют new и malloc, есть сторонние менеджеры памяти.
- new то же, что и malloc, только дополнительно вызывает конструкторы
- Внутри malloc есть буфер, если в буфере есть место, ваш вызов может выполниться быстро
- Если памяти в буфере нет, будет запрошена память у ОС (sbrk, VirtualAlloc) - это дорого
- ОС выделяет память страницами от 4Кб, а может быть и все 2Мб
- Стандартные аллокаторы универсальные, то есть должны быть потокобезопасны, быть одинаково эффективны для блоков разной длины, и 10 байт и 100Мб. Плата за универсальность - быстродействие
Глобальная память (data segment)
Если не удастся разместить блок глобальной памяти, то программа даже не запустится
Значение в квадратных скобках должно быть известно на этапе компиляции, увы
Фактически - это вычисление смещения:
Массив - непрерывный блок байт в памяти, sizeof(data) вернет размер массива в байтах (не элементах!). Размер массива в элементах можно вычислить: sizeof(data) / sizeof(data[0])
Измеряем скорость работы (benchmark)
- Измерений должно быть много
- Одному прогону верить нельзя
- Компилятор оптимизирует, надо ему мешать
- Перед тестами надо греться
Пример "вредной" оптимизации
Не даем компилятору оптимизировать
Написать свой аллокатор со стратегией линейного выделения памяти со следующим интерфейсом:
При вызове makeAllocator аллоцируется указанный размер, после чего при вызове alloc возвращает указатель на блок запрошенного размера или nullptr, если места недостаточно. После вызова reset аллокатор позволяет использовать свою память снова.
Аннотация: Проблемы при явном распределении памяти в Си++, способы их решения. Ссылки и указатели. Распределение памяти под переменные, управление памятью с помощью переопределения операторов new и delete.
Автоматические переменные
Самый простой метод – это объявление переменных внутри функций. Если переменная объявлена внутри функции, каждый раз, когда функция вызывается, под переменную автоматически отводится память. Когда функция завершается, память, занимаемая переменными, освобождается. Такие переменные называют автоматическими .
При создании автоматических переменных они никак не инициализируются, т.е. значение автоматической переменной сразу после ее создания не определено, и нельзя предсказать, каким будет это значение . Соответственно, перед использованием автоматических переменных необходимо либо явно инициализировать их, либо присвоить им какое-либо значение .
Аналогично автоматические переменные , объявленные внутри блока (последовательности операторов, заключенных в фигурные скобки ) создаются при входе в блок и уничтожаются при выходе из блока.
Замечание. Распространенной ошибкой является использование адреса автоматической переменной после выхода из функции. Конструкция типа:
дает непредсказуемый результат.
Динамическое и статическое выделение памяти. Преимущества и недостатки. Выделение памяти для одиночных переменных операторами new и delete . Возможные критические ситуации при выделении памяти. Инициализация при выделении памяти
Содержание
- 1. Пример динамического выделения памяти для структурной переменной
- 2. Пример динамического выделения памяти для объекта класса
- 3. Как выделить память для массива оператором new ? Общая форма
- 4. Как освободить память выделенную для массива оператором delete[] ? Общая форма
- 5. Пример динамического выделения и освобождения памяти для массива указателей на базовый тип
- 6. Пример выделения памяти для массива структурных переменных и его использование
- 7. Пример выделения и освобождения памяти для массива объектов. Инициализация массива объектов
- 8. Как перераспределить память, если нужно динамически увеличить (уменьшить) размер массива? Перераспределение памяти для структур, инициализация структур. Пример
Поиск на других ресурсах:
1. Пример динамического выделения памяти для структурной переменной
Выделение и освобождение памяти для структурной переменной. Пусть дана структура Date , которая имеет следующее описание:
Тогда, чтобы выделить и использовать память для переменной типа struct Date нужно написать приблизительно следующий код:
2. Пример динамического выделения памяти для объекта класса
В примере динамично выделяется память для указателя на объект класса CDayWeek . Пример реализован для приложения типа Console Application .
3. Как выделить память для массива оператором new ? Общая форма
Оператор new может быть использован для выделения памяти для массива. Общая форма оператора new в случае выделения памяти для массива:
- ptrArray – имя массива, для которого выделяется память;
- type – тип элементов массива. Тип элементов может быть базовый ( int , float , …) или другой структура, класс и т.п.);
- size – размер массива (количество элементов).
4. Как освободить память выделенную для массива оператором delete[] ? Общая форма
Для освобождения памяти, выделенной под массив, оператор delete имеет следующую форму использования:
где ptrArray – имя массива, для которого выделяется память.
5. Пример динамического выделения и освобождения памяти для массива указателей на базовый тип
В примере выделяется память для массива указателей на тип float . Затем элементы массива заполняются произвольными значениями. После этого, выделенная память освобождается оператором delete[] .
6. Пример выделения памяти для массива структурных переменных и его использование
В примере демонстрируется выделение и освобождение памяти для массива из 3-х структур типа TStudent . Также продемонстрированы способы доступа к полям заданного элемента в массиве структур.
7. Пример выделения и освобождения памяти для массива объектов. Инициализация массива объектов
В примере демонстрируется выделение памяти для массива объектов оператором new . После использования массива, происходит уничтожение выделенной памяти оператором delete .
В вышеприведенном коде, внутренняя переменная в массиве объектов инициализируется значением 1, так как такое значение задано в конструкторе без параметров CMonth()
Этот конструктор выступает инициализатором массива. Однако, в классе реализован еще один конструктор – конструктор с 1 параметром или параметризованный конструктор. Согласно синтаксису C++, массив объектов не может быть инициализирован параметризованным конструктором. Поэтому, в классе CMonth обязательно должен быть реализован конструктор без параметров.
Если конструктор без параметров CMonth() убрать из кода класса, то невозможно будет выделить память для массива объектов. Можно будет выделять память для одиночных объектов, но не для массива.
Вывод: если нужно выделить память для массива объектов некоторого класса, то этот класс обязательно должен иметь реализацию конструктора без параметров.
8. Как перераспределить память, если нужно динамически увеличить (уменьшить) размер массива? Перераспределение памяти для структур, инициализация структур. Пример
В примере демонстрируется процесс перераспределения памяти для типа структуры DayWeek . Выделение и перераспределение памяти динамически есть основным преимуществом этого способа по сравнению со статическим выделением памяти. Память в программе можно выделять когда нужно и сколько нужно.
В структуре DayWeek реализован конструктор без параметров (по умолчанию), который инициализирует массив структур значением по умолчанию ( d =1).
В функции main() сначала выделяется память для массива из 5 структур. Затем эта память перераспределяется для массива из 7 структур. Для этого используется дополнительный указатель p2 .
При перераспределении сначала память выделяется для p2 (7 элементов). Затем копируются данные из p в p2 . После этого освобождается память, которая была выделена для указателя p (5 элементов).
На следующем шаге значение p устанавливается равным значению p2 . Таким образом, оба указателя указывают на одну и ту же область памяти.
Читайте также: