Что такое статическая память c
Одна из причин сложности многопоточного программирования в том, что компилятор и аппаратное обеспечение могут слегка трансформировать операции программы с памятью такими способами, которые не влияют на однопоточное поведение, но могут затронуть многопоточное. Рассмотрим следующий метод:
Если _data и _initialized — обычные (т. е. неизменяемые) поля, компилятору и процессору разрешается такое переупорядочение операций, чтобы Init выполнялся так, будто он написан следующим образом:
Существуют различные оптимизации как в компиляторах, так и в процессорах, которые могут привести к такому переупорядочению, и это будет обсуждаться во второй статье.
В однопоточной программе переупорядочение выражений в Init ничего не меняет в программе. Пока и _initialized и _data обновляются до возврата управления этим методом, порядок присваиваний не имеет значения. В однопоточной программе нет второго потока, который мог бы наблюдать состояние между этими обновлениями.
Даже если компилятору и процессору разрешено переупорядочивать операции с памятью, это не означает, что на практике они всегда так делают.
Но в многопоточной программе порядок присваиваний может иметь значение, поскольку другой поток может считывать эти поля, пока Init находится в середине выполнения. Соответственно в переупорядоченной версии Init другой поток может наблюдать _initialized=true и _data=0.
Рис. 1. Код, подверженный риску переупорядочения операций с памятью
Допустим, что Init и Print вызываются параллельно (т. е. в разных потоках) в новом экземпляре DataInit. Если вы посмотрите код Init и Print, вам может показаться, что Print может выводить только «42» или «Not initialized». Но Print также может вывести «0».
Это переупорядочение не изменило бы поведение метода Init в однопоточной программе. Однако в многопоточной программе другой поток мог бы считать значения полей _initialized и _data после того, как Init модифицировал одно поле, но не успел сделать это со вторым, а затем последующее переупорядочение может изменить поведение программы. В результате метод Print мог бы вывести «0».
Переупорядочение Init — не единственный возможный источник проблем в этом примере кода. Даже если операции записи в Init не переупорядочиваются, операции чтения в методе Print могут быть трансформированы:
Как и в случае переупорядочения операций записи, эта трансформация никак не влияет на однопоточную программу, но может изменить поведение многопоточной. И равным образом переупорядочение операций чтения тоже может дать значение 0 в выводе.
Во второй части вы увидите, как и почему эти трансформации происходят на практике, когда мы будет подробно рассматривать различные аппаратные архитектуры.
Чтение изменяемого поля имеет семантику получения, т. е. эта операция не может быть переупорядочена с последующими операциями. Чтение изменяемого поля образует одностороннюю преграду: предшествующие операции могут проникать через нее, а последующие — нет. Возьмем такой пример:
Чтение 1 и чтение 3 — операции с неизменяемыми полями, а чтение 2 — операция с изменяемым полем. Чтение 2 нельзя переупорядочить с чтением 3, но можно — с чтением 1. В табл. 1 показаны допустимые переупорядочения в теле Foo.
Табл. 1. Допустимое переупорядочение операций чтения в AcquireSemanticsExample
int a = _a; // чтение 1
int b = _b; // чтение 2 (изменяемое поле)
int c = _c; // чтение 3
int b = _b; // чтение 2 (изменяемое поле)
int a = _a; // чтение 1
int c = _c; // чтение 3
int b = _b; // чтение 2 (изменяемое поле)
int c = _c; // чтение 3
int a = _a; // чтение 1
С другой стороны, операция записи в изменяемое поле имеет семантику освобождения, и поэтому ее нельзя переупорядочить с предыдущими операциями. Запись изменяемого поля образует одностороннюю преграду, как демонстрирует следующий пример:
Записи 1 и 3 — операции с неизменяемыми полями, а запись 2 — операция с изменяемым полем. Запись 2 нельзя переупорядочить с записью 1, но можно — с записью 3. В табл. 2 показаны допустимые переупорядочения в теле Foo.
Табл. 2. Допустимое переупорядочение операций записи в ReleaseSemanticsExample
_a = 1; // запись 1
_b = 1; // запись 2 (изменяемое поле)
_c = 1; // запись 3
_a = 1; // запись 1
_c = 1; // запись 3
_b = 1; // запись 2 (изменяемое поле)
_c = 1; // запись 3
_a = 1; // запись 1
_b = 1; // запись 2 (изменяемое поле)
Я вернусь к семантике получения-освобождения в подразделе «Публикация через изменяемое поле» далее в этой статье.
Если один поток повторно вызывает SetValue, а другой вызывает GetValue, то второй поток может наблюдать значение, которое никогда не записывалось первым потоком. Например, если первый поток попеременно вызывает SetValue со значениями Guid (0,0,0,0) и (5,5,5,5), то GetValue может наблюдать (0,0,0,5), (0,0,5,5) или (5,5,0,0), хотя ни одно из этих значений никогда не присваивалось через SetValue.
Причина такого «разрыва» в том, что присваивание _value = value не выполняется атомарно на аппаратном уровне. Аналогично чтение _value тоже не выполняется атомарно.
Оптимизации без переупорядочения Некоторые оптимизации компилятора могут вводить или исключать определенные операции с памятью. Так, компилятор может заменить повторяемые операции чтения какого-либо поля одним чтением. Аналогично, если код считывает поле и сохраняет значение в локальной переменной, а затем повторно считывает эту переменную, то компилятор мог бы вместо этого выбрать повторное чтение поля.
Один из подвохов в том, что типы, которые обычно считываются и записываются атомарно (например, int), могут считываться и записываться не атомарно, если значение неправильно выровнено в памяти.
Шаблоны взаимодействия потоков
Цель модели памяти — обеспечить взаимодействие потоков. Когда один из потоков записывает значения в память, а другой — считывает из памяти, модель памяти диктует, какие значения может увидеть читающий поток.
Блокировка Это самый простой способ совместного использования общих данных между потоками. При правильном использовании блокировок вам, в общем, не нужно беспокоиться о дополнительном исследовании модели памяти.
Всякий раз, когда поток захватывает блокировку, CLR гарантирует, что этот поток увидит все обновления, выполненные тем потоком, который владел блокировкой ранее. Добавим блокировку к примеру из начала этой статьи, как показано на рис. 2.
Рис. 2. Взаимодействие потоков с блокировкой
Добавление блокировки, которую поочередно захватывают Print и Set, дает простое решение. Теперь Set и Print выполняются атомарно по отношению друг к другу. Выражение lock гарантирует, что тела Print и Set будут выполняться в некоем последовательном порядке, даже если они вызываются из нескольких потоков.
Схема на рис. 3 показывает один из вариантов последовательного порядка, который мог бы наблюдаться, если бы поток 1 вызвал Print три раза, поток 2 вызвал Set один раз, а поток 3 вызвал Print один раз.
Блокировка — универсальный и мощный механизм разделения общего состояния между потоками.
Рис. 3. Последовательное выполнение с блокировкой
_lock | _lock |
Thread 1 | Поток 1 |
Thread 2 | Поток 2 |
Thread 3 | Поток 3 |
Set | Set |
Когда выполняется порция кода, владеющая блокировкой, она гарантированно видит результаты всех предшествующих в последовательном порядке операций записи. Кроме того, она гарантированно не видит результаты любых операций записи, выполняемых теми порциями кода, которые следуют за блокировкой.
Если в двух словах, то блокировки исключают всю непредсказуемость и сложность модели памяти: вам не надо беспокоиться о переупорядочении операций с памятью при правильном использовании блокировок. Но именно при правильном использовании. Если блокировку использует только Print или только Set (либо Print и Set захватывают две разные блокировки), операции с памятью становятся переупорядочиваемыми и сложность модели памяти вновь возвращается.
Публикация через API потоков Блокировка — универсальный и мощный механизм разделения общего состояния между потоками. Публикация через API потоков является еще одним часто применяемым шаблоном в программировании параллельной обработки.
Публикацию через API потоков легче всего пояснить на примере:
Изучив предыдущий пример кода, вы, вероятно, ожидали, что на экран будет выведено «42». И интуиция вас не подвела. Этот пример кода гарантированно выводит «42».
Может быть, это удивительно, что об этом вообще приходится упоминать, но на деле возможны реализации StartNew, которые выводили бы «0» вместо «42», по крайней мере теоретически. В конце концов, два потока взаимодействуют через неизменяемое поле, поэтому операции с памятью могут быть переупорядочены. Этот шаблон показан на схеме на рис. 4.
Рис. 4. Два потока, взаимодействующие через неизменяемое поле
Initial State | Начальное состояние |
Thread 1 | Поток 1 |
Thread 2 | Поток 2 |
Реализация StartNew должна гарантировать, что запись в s_value в потоке 1 не будет перемещена за <start task t>, а чтение из s_value в потоке 2 не будет перемещено до <task t starting>. И действительно StartNew API это гарантирует.
Публикация через инициализацию типа Другой способ надежной публикации некоего значения нескольким потокам — его запись в статическое поле в статическом инициализаторе или в статическом конструкторе. Возьмем пример:
Если Test3.PrintValue одновременно вызывается из нескольких потоков, гарантируется ли, что каждый вызов PrintValue выведет «42» и «false»? Или же один из вызовов приведет к выводу «0» или «true»? Как и в предыдущем случае, вы получаете именно то поведение, которое ожидаете: каждый поток гарантированно выводит «42» и «false».
Обсуждавшиеся до сих пор шаблоны ведут себя ожидаемым образом. Теперь перейдем к случаям, где поведение может оказаться неожиданным.
Шаблон, который я намерен обсудить, столь важен, что для него была разработана семантика ключевого слова volatile. По сути, лучший способ запомнить семантику ключевого слова volatile — запомнить этот шаблон, а не пытаться зазубривать абстрактные правила, пояснявшиеся ранее в этой статье.
Начнем с примера кода на рис. 5. В классе DataInit на рис. 5 два метода: Init и Print; оба могут быть вызваны из нескольких потоков. Если никакие операции с памятью не переупорядочиваются, Print может вывести только «Not initialized» или «42», но есть два возможных случая, когда Print мог бы вывести «0»:
- операции записи 1 и 2 были переупорядочены;
- операции чтения 1 и 2 были переупорядочены.
Рис. 5. Использование ключевого слова volatile
Если бы _initialized не была помечена как volatile, оба переупорядочения были бы разрешены. Однако, когда _initialized помечена как volatile, ни одно из этих переупорядочений не разрешено! В случае записи вы получаете обычную запись, за которой следует запись в изменяемое поле, а последнюю нельзя переупорядочить с предыдущей операцией в памяти. В случае чтения операция чтения из изменяемого поля сменяется обычной операцией чтения, а первую нельзя переупорядочить с последующей операцией в памяти.
Поэтому Print никогда не выведет «0», даже если она будет вызвана одновременно с Init в новом экземпляре DataInit.
Заметьте: если бы поле _data field было изменяемым, а _initialized — нет, оба переупорядочения были бы разрешены. В итоге этот пример является отличным способом запомнить семантику ключевого слова volatile.
Отложенная инициализация Одна из распространенных вариаций публикации через изменяемое поле — отложенная инициализация (lazy initialization) (рис. 6).
Рис. 6. Отложенная инициализация
В этом примере LazyGet всегда гарантированно возвращает «42». Однако, если бы поле _box field не было помечено как volatile, LazyGet могла бы вернуть «0» по двум причинам: могли бы быть переупорядочены либо операции чтения, либо операции записи.
Чтобы еще больше акцентировать на этом ваше внимание, рассмотрим такой класс:
Теперь возможно (по крайней мере, теоретически), что PrintValue выведет «0» из-за проблемы с моделью памятью. Вот пример использования BoxedInt, где такое разрешается:
Так как экземпляр BoxedInt был опубликован неправильно (через неизменяемое поле _box), поток, который вызывает Print, может наблюдать частично сконструированный объект! И вновь, сделав поле _box изменяемым, вы устраните проблему.
Interlocked-операции и барьеры памяти Interlocked-операции являются атомарными и иногда используются для уменьшения блокировок в многопоточных программах. Рассмотрим простой класс-счетчик, безопасный в многопоточной среде:
Используя Interlocked.Increment, вы можете переписать программу так:
При использовании Interlocked.Increment данный метод должен выполняться быстрее, по крайней мере в некоторых аппаратных архитектурах. В дополнение к операциям приращения класс Interlocked (bit.ly/RksCMF) предоставляет методы для различных атомарных операций: добавления значения, замены значения по условию, замены значения и возврата исходного значения и т. д.
Все Interlocked-методы имеют одно очень интересное свойство: их нельзя переупорядочивать с другими операциями с памятью.
Все Interlocked-методы имеют одно очень интересное свойство: их нельзя переупорядочивать с другими операциями с памятью. Операция, тесно связанная с Interlocked-методами, — Thread.MemoryBarrier, которую можно рассматривать как пустую Interlocked-операцию. Так же, как Interlocked-метод, Thread.MemoryBarrier нельзя переупорядочить с любой предыдущей или последующей операцией с памятью. Однако в отличие от Interlocked-метода Thread.MemoryBarrier не имеет побочного эффекта; он просто ограничивает переупорядочения.
Цикл опроса Это шаблон, который, как правило, не рекомендуется, но, к сожалению, часто используется на практике. Неправильный цикл опроса показан на рис. 7.
Рис. 7. Неправильный цикл опроса
В этом примере основной поток крутится в цикле, опрашивая конкретное неизменяемое поле. Вспомогательный поток тем временем присваивает значение этому полю, но основной поток никогда не увидит измененное значение.
Рекомендации
С одной стороны, спецификация утверждает, что только изменяемые поля подчиняются семантике получения-освобождения (acquire-release semantics), чего вроде бы недостаточно для предотвращения изъятия операции чтения изменяемого поля из цикла опроса. С другой — пример кода в этой спецификации действительно опрашивает изменяемое поле, а это подразумевает, что операция чтения изменяемого поля не может быть изъята из данного цикла.
На аппаратных платформах x86 и x64 PollingLoopExample.Main будет, как правило, зависать. JIT-компилятор считает поле test1._loop только один раз, сохранит его значение в одном из регистров, а затем будет крутиться в цикле, пока значение в этом регистре не изменится, чего не произойдет никогда.
Однако, если тело цикла содержит некоторые выражения, JIT-компилятору, возможно, потребуется тот же регистр для других целей, поэтому каждая итерация может приводить к повторному чтению test1._loop. В итоге вы можете столкнуться с циклами в существующих программах, которые опрашивают неизменяемое поле и ухитряются нормально работать.
Тип | Описание |
Lazy<> | Значения с отложенной инициализацией |
LazyInitializer | |
BlockingCollection<> | Наборы, безопасные в многопоточной среде |
ConcurrentBag<> | |
ConcurrentDictionary<,> | |
ConcurrentQueue<> | |
ConcurrentStack<> | |
AutoResetEvent | Примитивы для координации выполнения в разных потоках |
Barrier | |
CountdownEvent | |
ManualResetEventSlim | |
Monitor | |
SemaphoreSlim | |
ThreadLocal<> | Контейнер, хранящий отдельное значение для каждого потока |
Используя эти примитивы, зачастую можно избежать низкоуровневого кода, зависимого от модели памяти весьма сложным для понимания образом (через volatile и прочее).
В следующей части
Во второй части этой статьи я поясню, как модель памяти на деле реализуется в различных аппаратных архитектурах, что поможет в понимании поведения настоящих программ.
Выражаю благодарность за рецензирование статьи экспертам Джо Даффи(Joe Duffy), Эрику Ейлебрехту(Eric Eilebrecht), Джо Хоугу(Joe Hoag), Эмаду Омара(Emad Omara), Гранту Рихинсу(Grant Richins), Ярославу Шевчуку(Jaroslav Sevcik) и Стефену Таубу (Stephen Toub).
В традиционных языках программирования, таких как Си , Фортран, Паскаль , существуют три вида памяти: статическая, стековая и динамическая . Конечно, с физической точки зрения никаких различных видов памяти нет: оперативная память - это массив байтов, каждый байт имеет адрес , начиная с нуля. Когда говорится о видах памяти, имеются в виду способы организации работы с ней, включая выделение и освобождение памяти , а также методы доступа.
Статическая память
Статическая память выделяется еще до начала работы программы, на стадии компиляции и сборки. Статические переменные имеют фиксированный адрес, известный до запуска программы и не изменяющийся в процессе ее работы. Статические переменные создаются и инициализируются до входа в функцию main , с которой начинается выполнение программы.
Существует два типа статических переменных:
- глобальные переменные - это переменные, определенные вне функций, в описании которых отсутствует слово static . Обычно описания глобальных переменных, включающие слово extern , выносятся в заголовочные файлы (h-файлы). Слово extern означает, что переменная описывается, но не создается в данной точке программы. Определения глобальных переменных, т.е. описания без слова extern , помещаются в файлы реализации (c-файлы или cpp-файлы). Пример: глобальная переменная maxind описывается дважды:
- в h-файле с помощью строки
- Статическую переменную можно описать и внутри функции, хотя обычно так никто не делает. Переменная размещается не в стеке, а в статической памяти, т.е. ее нельзя использовать при рекурсии, а ее значение сохраняется между различными входами в функцию. Область видимости такой переменной ограничена телом функции, в которой она определена. В остальном она подобна статической или глобальной переменной. Заметим, что ключевое слово static в языке Си используется для двух различных целей:
- как указание типа памяти: переменная располагается в статической памяти, а не в стеке;
- как способ ограничить область видимости переменной рамками одного файла (в случае описания переменной вне функции).
Стековая, или локальная, память
Локальные, или стековые, переменные - это переменные, описанные внутри функции. Память для таких переменных выделяется в аппаратном стеке, см. раздел 2.3.2. Память выделяется в момент входа в функцию или блок и освобождается в момент выхода из функции или блока. При этом захват и освобождение памяти происходят практически мгновенно, т.к. компьютер только изменяет регистр, содержащий адрес вершины стека.
Локальные переменные можно использовать при рекурсии, поскольку при повторном входе в функцию в стеке создается новый набор локальных переменных, а предыдущий набор не разрушается. По этой же причине локальные переменные безопасны при использовании нитей в параллельном программировании (см. раздел 2.6.2). Программисты называют такое свойство функции реентерабельностью, от англ. re-enter able - возможность повторного входа. Это очень важное качество с точки зрения надежности и безопасности программы! Программа, работающая со статическими переменными, этим свойством не обладает, поэтому для защиты статических переменных приходится использовать механизмы синхронизации (см. 2.6.2), а логика программы резко усложняется. Всегда следует избегать использования глобальных и статических переменных, если можно обойтись локальными.
Недостатки локальных переменных являются продолжением их достоинств. Локальные переменные создаются при входе в функцию и исчезают после выхода из нее, поэтому их нельзя использовать в качестве данных, разделяемых между несколькими функциями. К тому же, размер аппаратного стека не бесконечен, стек может в один прекрасный момент переполниться (например, при глубокой рекурсии), что приведет к катастрофическому завершению программы. Поэтому локальные переменные не должны иметь большого размера. В частности, нельзя использовать большие массивы в качестве локальных переменных.
Динамическая память, или куча
Помимо статической и стековой памяти, существует еще практически неограниченный ресурс памяти, которая называется динамическая, или куча ( heap ). Программа может захватывать участки динамической памяти нужного размера. После использования ранее захваченный участок динамической памяти следует освободить.
Под динамическую память отводится пространство виртуальной памяти процесса между статической памятью и стеком. (Механизм виртуальной памяти был рассмотрен в разделе 2.6.) Обычно стек располагается в старших адресах виртуальной памяти и растет в сторону уменьшения адресов (см. раздел 2.3). Программа и константные данные размещаются в младших адресах, выше располагаются статические переменные. Пространство выше статических переменных и ниже стека занимает динамическая память:
Азы: статическая память, динамическая память, автоматическая память
столько раз приходистя возвращаться к началу, и все благодаря моим преподавателям, которые не.Статическая память,Динамическая память.
a) Статическая память. Двумерный массив. Дан массив целых чисел. В массиве есть отрицательные.Статическая или динамическая библиотека
Хочу создать свои функции? Какие библиотеки лучше использовать?Динамическая строка или статическая?
nonedark2008, а что все-таки попадает в кучу, и куда попадает все остальное? И почему 2. Нет ? Помогите пожалуйста разобраться детально с этими вещами.
В функцию в качестве параметра передаётся указать на строку символов. Можно ли как то в нутри.Статический переменные попадают в стек, динамические - в кучу.
На счет второго я ошибся, неправильно вопрос прочитал. Там ответ да.
Добавлено через 11 минут
Я опять не подумал и обманул тебя.
В кучу идут динамические объекты, в стек - локальные. Статические переменные, глобальные переменные обычно хранятся в отдельном участке памяти.Program code and data.Code begins at the same fixed address for all processes,
followed by data locations that correspond to global C variables. The code and
data areas are initialized directly from the contents of an executable object file.Heap.The code and data areas are followed immediately by the run-time heap.
Unlike the code and data areas, which are fixed in size once the process begins
running, the heap expands and contracts dynamically at run time as a result
of calls to C standard library routines such as malloc and free.Shared libraries.Near the middle of the address space is an area that holds the
code and data for shared libraries such as the C standard library and the math
library.Stack. At the top of the user’s virtual address space is the user stack that
the compiler uses to implement function calls. Like the heap, the user stack
expands and contracts dynamically during the execution of the program. In
particular, each time we call a function, the stack grows. Each time we return
from a function, it contracts.Kernel virtual memory. The kernel is the part of the operating system that is
always resident in memory. The top region of the address space is reserved for
the kernel. Application programs are not allowed to read or write the contents
of this area or to directly call functions defined in the kernel code.Динамическое и статическое выделение памяти. Преимущества и недостатки. Выделение памяти для одиночных переменных операторами new и delete . Возможные критические ситуации при выделении памяти. Инициализация при выделении памяти
Содержание
- 1. Динамическое и статическое (фиксированное) выделение памяти. Главные различия
- 2. Преимущества и недостатки использования динамического и статического способов выделения памяти
- 3. Как выделить память оператором new для одиночной переменной? Общая форма.
- 4. Как освободить память, выделенную под одиночную переменную оператором delete ? Общая форма
- 5. Примеры выделения ( new ) и освобождения ( delete ) памяти для указателей базовых типов
- 6. Что такое «утечка памяти» (memory leak)?
- 7. Каким образом выделить память оператором new с перехватом критической ситуации, при которой память может не выделиться? Исключительная ситуацияbad_alloc . Пример
- 8. Выделение памяти для переменной с одновременной инициализацией. Общая форма. Пример
Поиск на других ресурсах:
1. Динамическое и статическое (фиксированное) выделение памяти. Главные различия
Для работы с массивами информации, программы должны выделять память для этих массивов. Для выделения памяти под массивы переменных используются соответствующие операторы, функции и т.п.. В языке программирования C++ выделяют следующие способы выделения памяти:
1. Статическое (фиксированное) выделение памяти. В этом случае память выделяется только один раз во время компиляции. Размер выделенной памяти есть фиксированным и неизменным до конца выполнения программы. Примером такого выделения может служить объявление массива из 10 целых чисел:
2. Преимущества и недостатки использования динамического и статического способов выделения памяти
Динамическое выделение памяти по сравнению со статическим выделением памяти дает следующие преимущества:
- память выделяется по мере необходимости программным путем;
- нет лишних затрат неиспользованной памяти. Выделяется столько памяти сколько нужно и если нужно;
- можно выделять память для массивов информации, размер которых заведомо неизвестен. Определение размера массива формируется в процессе выполнения программы;
- удобно осуществлять перераспределение памяти. Или другими словами, удобно выделять новый фрагмент для одного и того же массива, если нужно выделить дополнительную память или освободить ненужную;
- при статическом способе выделения памяти трудно перераспределять память для переменной-массива, поскольку она уже выделена фиксировано. В случае динамического способа выделения, это делается просто и удобно.
Преимущества статического способа выделения памяти:
- статическое (фиксированное) выделение памяти лучше использовать, когда размер массива информации заведомо известен и есть неизменным на протяжении выполнения всей программы;
- статическое выделение памяти не требует дополнительных операций освобождения с помощью оператора delete . Отсюда вытекает уменьшение ошибок программирования. Каждому оператору new должен соответствовать свой оператор delete ;
- естественность (натуральность) представления программного кода, который оперирует статическими массивами.
В зависимости от поставленной задачи, программист должен уметь правильно определить, какой способ выделения памяти подходит для той или другой переменной (массива).
3. Как выделить память оператором new для одиночной переменной? Общая форма.
Общая форма выделения памяти для одиночной переменной оператором new имеет следующий вид:
- ptrName – имя переменной (указателя), которая будет указывать на выделенную память;
- type – тип переменной. Размер памяти выделяется достаточный для помещения в нее значения переменной данного типа type .
4. Как освободить память, выделенную под одиночную переменную оператором delete ? Общая форма
Общая форма оператора delete для одиночной переменной:
где ptrName – имя указателя, для которого была раньше выделена память оператором new . После выполнения оператора delete указатель ptrName указывает на произвольный участок памяти, который не является зарезервированным (выделенным).
5. Примеры выделения ( new ) и освобождения ( delete ) памяти для указателей базовых типов
В примерах демонстрируется использование операторов new и delete . Примеры имеют упрощенный вид.
Пример 1. Указатель на тип int . Простейший пример
Пример 2. Указатель на тип double
⇑
7. Каким образом выделить память оператором new с перехватом критической ситуации, при которой память может не выделиться? Исключительная ситуация bad_alloc . Пример
При использовании оператора new возможна ситуация, когда память не выделится. Память может не выделиться в следующих ситуациях:
- если отсутствует свободная память;
- размер свободной памяти меньше чем тот, который был задан в операторе new .
В этом случае генерируется исключительная ситуация bad_alloc . Программа может перехватить эту ситуацию и соответствующим образом обработать ее.
Пример. В примере учитывается ситуация, когда память может не выделиться оператором new . В таком случае осуществляется попытка выделить память. Если попытка удачная, то работа программы продолжается. Если попытка завершилась неудачей, то происходит выход из функции с кодом -1.
8. Выделение памяти для переменной с одновременной инициализацией. Общая форма. Пример
Оператор выделения памяти new для одиночной переменной допускает одновременную инициализацию значением этой переменной.
В общем, выделение памяти для переменной с одновременной инициализацией имеет вид
- ptrName – имя переменной-указателя, для которой выделяется память;
- type – тип на который указывает указатель ptrName ;
- value – значение, которое устанавливается для выделенного участка памяти (значение по указателю).
Пример. Выделение памяти для переменных с одновременной инициализацией. Ниже приводится функция main() для консольного приложения. Продемонстрировано выделение памяти с одновременной инициализацией. Также учитывается ситуация, когда попытка выделить память завершается неудачей (критическая ситуация bad_alloc ).
Читайте также: