Выберите правильный вариант выделения динамической памяти под переменную x типа float

Обновлено: 05.07.2024

1. Функция вычисляет произведение двух чисел. Исходные данные вводятся с клавиатуры. Какие проверки целесообразно ввести в программе:
а) проверка, что исходные данные являются числами +
б) проверки не нужны, все возможные ошибки отловит компилятор
в) проверка исходных данных на равенство нулю

2. Для чего предназначен оператор namespace:
а) для использования классов, переменных и функций из других модулей программы без использования заголовочных файлов
б) для заключения в группу объявлений классов, переменных и функций в отдельный контекст со своим именем +
в) для заключения в группу объявлений классов, переменных и функций для использования только в текущем модуле

3. Какой из компонентов может входить в интегрированную среду программирования:
а) наладчик
б) доводчик
в) отладчик +

4. Какой из компонентов может входить в интегрированную среду программирования:
а) текстовый редактор +
б) текстовый директор
в) текстовый модератор

5. Какой из компонентов может входить в интегрированную среду программирования:
а) регулятор
б) доминатор
в) компилятор +

6. Если определена операция вычитания для двух объектов класса A, а операция преобразования к int не определена, что будет вызвано при:
A a1,a2,a3=5;
a3 = a1 – a2;
а) только операция вычитания
б) произойдет ошибка +
в) преобразование к целому

7. Какой из наборов перечисляемых значений записан правильно:
а) enum < a, b = 3, c = 4, 3 >;
б) enum < a, b, 3, 4 >;
в) enum ; +

9. Чему будет равен результат вычисления выражения: int d=5; bool b = true, c; c = (!b||(d>3)):
а) Ошибка компилятора
б) false
в) true +

10. Если в арифметическом выражении участвуют целый и вещественный операнды, то:
а) ошибка компиляции
б) целый тип приводится к вещественному +
в) вещественный тип приводится к целому

11. Укажите в каком выражении произойдет потеря точности:
а) int i; float x = 2.134, y = 3.14; i = x/y; +
б) short i = 0x3; float x = 2.7, v; v = i + x;
в) float M = 235.2; double Z = 3; Z *= M;

12. Если после выражения стоит точка с запятой, то:
а) выражение вычисляется, а его значение запоминается в специальной переменной, которую можно использовать в следующем операторе
б) это оператор-выражение, действие которого заключается в вычислении выражения +
в) выражение вычисляется только если первой стоит операция присваивания

13. Что из себя представляет динамическое выделение памяти:
а) память под объект (переменную) может выделяться не сразу, а в процессе работы программы, освобождение памяти производится вручную +
б) память под объект (переменную) может выделяться не сразу, а в процессе работы программы, освобождение памяти производится автоматически после завершения программы
в) память под объект (переменную) выделяется каждый раз при обращении к переменной

14. Отметьте истинное высказывание:
а) переменная инициализируется, потом объявляется
б) переменная объявляется, потом инициализируется и изменяется
в) переменная объявляется, потом изменяется +

15. Какие операции поддаются перегрузке:
а) унарные и бинарные +
б) только бинарные
в) только унарные

16. Переменная типа signed char может принимать значения:
а) только символов английского алфавита, цифр и символа подчеркивания
б) из первой половины кодовой таблицы +
в) только из алфавита языка C++

17. Переменная типа signed char может принимать значения:
а) только из алфавита языка C++
б) только символов английского алфавита, цифр и символа подчеркивания
в) от -128 до 127 +

18. В переменной типа unsigned char можно хранить число:
а) -213
б) 213 +
в) 1213

19. В переменной типа unsigned char можно хранить число:
а) -13
б) 1213
в) 13 +

20. Чему равно числовое значение выражения e/2*a-abs(e)*1e0 при e = 4, a = 2:
а) 3
б) 0 +
в) 1

21. Выберите правильное утверждение:
а) целой переменной можно присвоить вещественную константу +
б) целой константе можно присвоить целую переменную
в) целой константе можно присвоить вещественную переменную

22. Выберите правильное утверждение:
а) целой константе можно присвоить целую переменную
б) целой переменной можно присвоить целую константу +
в) целой константе можно присвоить вещественную переменную

23. Чему равно значение выражения (a && ! b || c), где a, b и с -величины типа bool, имеющие значения false, true и true соответственно:
а) false
б) yes
в) true +

24. Какое выражение не содержат синтаксических ошибок:
а) sin(abs(0.6(e*3))
б) ((cos(3*a+1.*abs(x)))) +
в) a*exp(t)\(2t)

25. Какое выражение не содержат синтаксических ошибок:
а) a*exp(t)\(2t)
б) sin(abs(0.6(e*3))
в) 0XCC00*.34E-4/_do/k-2 +

26. Чему равно числовое значение выражения sqrt(4)+142/20*2:
а) 5
б) 16 +
в) 9

27. Какая из следующих операций языка C выполняется справа налево:
а) = +
б) ->
в) []

28. Какое выражение не содержит синтаксических ошибок:
а) (-0.18)*a)/(r-0.2*t)
б) (-0.18)*a)\(r-0.2*t))
в) -0.18*a/r-0.2*t +

29. При использовании в программе функции scanf требуется разделять числовые значения величин при вводе при помощи:
а) нажатия клавиш TAB или ENTER +
б) клавиш курсора
в) пробела или запятой

30. При использовании в программе функции scanf требуется разделять числовые значения величин при вводе при помощи:
а) пробела или запятой
б) по крайней мере одного пробела +
в) клавиш курсора

Статическое выделение памяти выполняется для статических и глобальных переменных. Память выделяется один раз (при запуске программы) и сохраняется на протяжении работы всей программы.

Автоматическое выделение памяти выполняется для параметров функции и локальных переменных. Память выделяется при входе в блок, в котором находятся эти переменные, и удаляется при выходе из него.

Динамическое выделение памяти является темой этого урока.

Динамическое выделение переменных

Как статическое, так и автоматическое распределение памяти имеют два общих свойства:

Размер переменной/массива должен быть известен во время компиляции.

Выделение и освобождение памяти происходит автоматически (когда переменная создается/уничтожается).

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

Например, при использовании строки для хранения имени пользователя, мы не знаем наперед насколько длинным оно будет, пока пользователь его не введет. Или нам нужно создать игру с непостоянным количеством монстров (во время игры одни монстры умирают, другие появляются, пытаясь, таким образом, убить игрока).

Если нам нужно объявить размер всех переменных во время компиляции, то самое лучшее, что мы можем сделать — это попытаться угадать их максимальный размер, надеясь, что этого будет достаточно:

char name [ 30 ] ; // будем надеяться, что пользователь введет имя длиной менее 30 символов! Polygon rendering [ 40000 ] ; // этому 3D-рендерингу лучше состоять из менее чем 40000 полигонов!

Это плохое решение, по крайней мере, по трем причинам:

Во-первых, теряется память, если переменные фактически не используются или используются, но не все. Например, если мы выделим 30 символов для каждого имени, но имена в среднем будут занимать по 15 символов, то потребление памяти получится в два раза больше, чем нам нужно на самом деле. Или рассмотрим массив rendering : если он использует только 20 000 полигонов, то память для других 20 000 полигонов фактически тратится впустую (т.е. не используется)!

В Visual Studio это можно проверить, запустив следующий фрагмент кода:

int array [ 1000000000 ] ; // выделяем 1 миллиард целочисленных значений

Лимит в 1МБ памяти может быть проблематичным для многих программ, особенно где используется графика.

Для динамического выделения памяти одной переменной используется оператор new:

new int ; // динамически выделяем целочисленную переменную и сразу же отбрасываем результат (так как нигде его не сохраняем)

В примере, приведенном выше, мы запрашиваем выделение памяти для целочисленной переменной из операционной системы. Оператор new возвращает указатель, содержащий адрес выделенной памяти.

Для доступа к выделенной памяти создается указатель:

int * ptr = new int ; // динамически выделяем целочисленную переменную и присваиваем её адрес ptr, чтобы затем иметь доступ к ней

Затем мы можем разыменовать указатель для получения значения:

* ptr = 8 ; // присваиваем значение 8 только что выделенной памяти

Вот один из случаев, когда указатели полезны. Без указателя с адресом на только что выделенную память у нас не было бы способа получить доступ к ней.

Как работает динамическое выделение памяти?

На вашем компьютере имеется память (возможно, большая её часть), которая доступна для использования программами. При запуске программы ваша операционная система загружает эту программу в некоторую часть этой памяти. И эта память, используемая вашей программой, разделена на несколько частей, каждая из которых выполняет определенную задачу. Одна часть содержит ваш код, другая используется для выполнения обычных операций (отслеживание вызываемых функций, создание и уничтожение глобальных и локальных переменных и т.д.). Мы поговорим об этом чуть позже. Тем не менее, большая часть доступной памяти компьютера просто находится в ожидании запросов на выделение от программ.

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

В отличие от статического или автоматического выделения памяти, программа самостоятельно отвечает за запрос и обратный возврат динамически выделенной памяти.

Освобождение памяти

Когда вы динамически выделяете переменную, то вы также можете её инициализировать посредством прямой инициализации или uniform-инициализации (в С++11):

int * ptr1 = new int ( 7 ) ; // используем прямую инициализацию int * ptr2 = new int < 8 >; // используем uniform-инициализацию

Когда уже всё, что требовалось, выполнено с динамически выделенной переменной — нужно явно указать для С++ освободить эту память. Для переменных это выполняется с помощью оператора delete:

// Предположим, что ptr ранее уже был выделен с помощью оператора new delete ptr ; // возвращаем память, на которую указывал ptr, обратно в операционную систему ptr = 0 ; // делаем ptr нулевым указателем (используйте nullptr вместо 0 в C++11)

Оператор delete на самом деле ничего не удаляет. Он просто возвращает память, которая была выделена ранее, обратно в операционную систему. Затем операционная система может переназначить эту память другому приложению (или этому же снова).

Хотя может показаться, что мы удаляем переменную, но это не так! Переменная-указатель по-прежнему имеет ту же область видимости, что и раньше, и ей можно присвоить новое значение, как и любой другой переменной.

Обратите внимание, удаление указателя, не указывающего на динамически выделенную память, может привести к проблемам.

Висячие указатели

Язык C++ не предоставляет никаких гарантий относительно того, что произойдет с содержимым освобожденной памяти или со значением удаляемого указателя. В большинстве случаев, память, возвращаемая операционной системе, будет содержать те же значения, которые были у нее до освобождения, а указатель так и останется указывать на только что освобожденную (удаленную) память.

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

int * ptr = new int ; // динамически выделяем целочисленную переменную * ptr = 8 ; // помещаем значение в выделенную ячейку памяти delete ptr ; // возвращаем память обратно в операционную систему, ptr теперь является висячим указателем std :: cout << * ptr ; // разыменование висячего указателя приведет к неожиданным результатам delete ptr ; // попытка освободить память снова приведет к неожиданным результатам также

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

Процесс освобождения памяти может также привести и к созданию нескольких висячих указателей. Рассмотрим следующий пример:

int * ptr = new int ; // динамически выделяем целочисленную переменную int * otherPtr = ptr ; // otherPtr теперь указывает на ту же самую выделенную память, что и ptr delete ptr ; // возвращаем память обратно в операционную систему. ptr и otherPtr теперь висячие указатели // Однако, otherPtr по-прежнему является висячим указателем!

Есть несколько рекомендаций, которые могут здесь помочь:

Во-первых, старайтесь избегать ситуаций, когда несколько указателей указывают на одну и ту же часть выделенной памяти. Если это невозможно, то выясните, какой указатель из всех «владеет» памятью (и отвечает за её удаление), а какие указатели просто получают доступ к ней.

Правило: Присваивайте удаленным указателям значение 0 (или nullptr в C++11), если они не выходят из области видимости сразу же после удаления.

Оператор new

При запросе памяти из операционной системы в редких случаях она может быть не выделена (т.е. её может и не быть в наличии).

По умолчанию, если оператор new не сработал, память не выделилась, то генерируется исключение bad_alloc . Если это исключение будет неправильно обработано (а именно так и будет, поскольку мы еще не рассматривали исключения и их обработку), то программа просто прекратит свое выполнение (произойдет сбой) с ошибкой необработанного исключения.

Во многих случаях процесс генерации исключения оператором new (как и сбой программы) нежелателен, поэтому есть альтернативная форма оператора new, которая возвращает нулевой указатель, если память не может быть выделена. Нужно просто добавить константу std::nothrow между ключевым словом new и типом данных:

int * value = new ( std :: nothrow ) int ; // указатель value станет нулевым, если динамическое выделение целочисленной переменной не выполнится

В примере, приведенном выше, если оператор new не возвратит указатель с динамически выделенной памятью, то возвратится нулевой указатель.

Разыменовывать его также не рекомендуется, так как это приведет к неожиданным результатам (скорее всего, к сбою в программе). Поэтому наилучшей практикой является проверка всех запросов на выделение памяти для обеспечения того, что эти запросы будут выполнены успешно и память выделится:

int * value = new ( std :: nothrow ) int ; // запрос на выделение динамической памяти для целочисленного значения if ( ! value ) // обрабатываем случай, когда new возвращает null (т.е. память не выделяется)

Поскольку не выделение памяти оператором new происходит крайне редко, то обычно программисты забывают выполнять эту проверку!

Нулевые указатели и динамическое выделение памяти

Нулевые указатели (указатели со значением 0 или nullptr ) особенно полезны в процессе динамического выделения памяти. Их наличие как бы сообщаем нам: «Этому указателю не выделено никакой памяти». А это, в свою очередь, можно использовать для выполнения условного выделения памяти:

// Если для ptr до сих пор не выделено памяти, то выделяем её

Удаление нулевого указателя ни на что не влияет. Таким образом, в следующем нет необходимости:

Вместо этого вы можете просто написать:

Если ptr не является нулевым, то динамически выделенная переменная будет удалена. Если значением указателя является нуль, то ничего не произойдет.

Утечка памяти

Динамически выделенная память не имеет области видимости, т.е. она остается выделенной до тех пор, пока не будет явно освобождена или пока ваша программа не завершит свое выполнение (и операционная система очистит все буфера памяти самостоятельно). Однако указатели, используемые для хранения динамически выделенных адресов памяти, следуют правилам области видимости обычных переменных. Это несоответствие может вызвать интересное поведение, например:

Здесь мы динамически выделяем целочисленную переменную, но никогда не освобождаем память через использование оператора delete. Поскольку указатели следуют всем тем же правилам, что и обычные переменные, то, когда функция завершит свое выполнение, ptr выйдет из области видимости. Поскольку ptr — это единственная переменная, хранящая адрес динамически выделенной целочисленной переменной, то, когда ptr уничтожится, больше не останется указателей на динамически выделенную память. Это означает, что программа «потеряет» адрес динамически выделенной памяти. И в результате эту динамически выделенную целочисленную переменную нельзя будет удалить.

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

Хотя утечка памяти может возникнуть и из-за того, что указатель выходит из области видимости, возможны и другие способы, которые могут привести к утечкам памяти. Например, если указателю, хранящему адрес динамически выделенной памяти, присвоить другое значение:

В предыдущей главе уже обсуждалось, что локальные переменные кладутся на стек и существую до тех пор, пока мы не вышли из функции. С одной стороны, это позволяет автоматически очищать память, с другой стороны, существует необходимость в переменных, время жизни которых мы можем контролировать самостоятельно. Кроме того, нам необходимо динамическое выделение памяти, когда размер используемого пространства заранее не известен. Для этого используется выделение памяти на куче. Недостатков у такого подхода два: во-первых, память необходимо вручную очищать, во-вторых, выдеение памяти – достаточно дорогостоящая операция.

Для выделения памяти на куче в си используется функция malloc (memory allocation) из библиотеки stdlib.h

Функция выделяет size байтов памяти и возвращает указатель на неё. Если память выделить не удалось, то функция возвращает NULL. Так как malloc возвращает указатель типа void, то его необходимо явно приводить к нужному нам типу. Например, создадим указатель, после этого выделим память размером в 100 байт.

После того, как мы поработали с памятью, необходимо освободить память функцией free.
Используя указатель, можно работать с выделенной памятью как с массивом. Пример: пользователь вводит число – размер массива, создаём массив этого размера и заполняем его квадратами чисел по порядку. После этого выводим и удаляем массив.

Здесь (int *) – приведение типов. Пишем такой же тип, как и у указателя.
size * sizeof(int) – сколько байт выделить. sizeof(int) – размер одного элемента массива.
После этого работаем с указателем точно также, как и с массивом. В конце не забываем удалять выделенную память.

Теперь представим на рисунке, что у нас происходило. Пусть мы ввели число 5.

Функция malloc выделила память на куче по определённому адресу, после чего вернула его. Теперь указатель p хранит этот адрес и может им пользоваться для работы. В принципе, он может пользоваться и любым другим адресом.
Когда функция malloc "выделяет память", то она резервирует место на куче и возвращает адрес этого участка. У нас будет гарантия, что компьютер не отдаст нашу память кому-то ещё. Когда мы вызываем функцию free, то мы освобождаем память, то есть говорим компьютеру, что эта память может быть использована кем-то другим. Он может использовать нашу память, а может и нет, но теперь у нас уже нет гарантии, что эта память наша. При этом сама переменная не зануляется, она продолжает хранить адрес, которым ранее пользовалась.

Это очень похоже на съём номера в отеле. Мы получаем дубликат ключа от номера, живём в нём, а потом сдаём комнату обратно. Но дубликат ключа у нас остаётся. Всегда можно зайти в этот номер, но в нём уже кто-то может жить. Так что наша обязанность – удалить дубликат.

Иногда думают, что происходит "создание" или "удаление" памяти. На самом деле происходит только перераспределение ресурсов.

Освобождение памяти с помощью free

Т еперь рассмотри, как происходит освобождение памяти. Переменная указатель хранит адрес области памяти, начиная с которого она может им пользоваться. Однако, она не хранит размера этой области. Откуда тогда функция free знает, сколько памяти необходимо освободить?

  • 1. Можно создать карту, в которой будет храниться размер выделенного участка. Каждый раз при освобождении памяти компьютер будет обращаться к этим данным и получать нужную информацию.
  • 2. Второе решение более распространено. Информация о размере хранится на куче до самих данных. Таким образом, при выделении памяти резервируется места больше и туда записывается информация о выделенном участке. При освобождении памяти функция free "подсматривает", сколько памяти необходимо удалить.

Работа с двумерными и многомерными массивами

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

  • 1. Создавать массивы "неправильной формы", то есть массив строк, каждая из которых имеет свой размер.
  • 2. Работать по отдельности с каждой строкой массива: освобождать память или изменять размер строки.

Создадим "треугольный" массив и заполним его значениями

Чтобы создать трёхмерный массив, по аналогии, необходимо сначала определить указатель на указатель на указатель, после чего выделить память под массив указателей на указатель, после чего проинициализировать каждый из массивов и т.д.

calloc

Ф ункция calloc выделяет n объектов размером m и заполняет их нулями. Обычно она используется для выделения памяти под массивы. Синтаксис

realloc

Е щё одна важная функция – realloc (re-allocation). Она позволяет изменить размер ранее выделенной памяти и получает в качестве аргументов старый указатель и новый размер памяти в байтах:

Функция realloc может как использовать ранее выделенный участок памяти, так и новый. При этом не важно, меньше или больше новый размер – менеджер памяти сам решает, где выделять память.
Пример – пользователь вводит слова. Для начала выделяем под слова массив размером 10. Если пользователь ввёл больше слов, то изменяем его размер, чтобы хватило места. Когда пользователь вводит слово end, прекращаем ввод и выводим на печать все слова.

Хочу обратить внимание, что мы при выделении памяти пишем sizeof(char*), потому что размер указателя на char не равен одному байту, как размер переменной типа char.

Ошибки при выделении памяти

1. Бывает ситуация, при которой память не может быть выделена. В этом случае функция malloc (и calloc) возвращает NULL. Поэтому, перед выделением памяти необходимо обнулить указатель, а после выделения проверить, не равен ли он NULL. Так же ведёт себя и realloc. Когда мы используем функцию free проверять на NULL нет необходимости, так как согласно документации free(NULL) не производит никаких действий. Применительно к последнему примеру:

Хотелось бы добавить, что ошибки выделения памяти могут случиться, и просто выходить из приложения и выкидывать ошибку плохо. Решение зависит от ситуации. Например, если не хватает памяти, то можно подождать некоторое время и после этого опять попытаться выделить память, или использовать для временного хранения файл и переместить туда часть объектов. Или выполнить очистку, сократив используемую память и удалив ненужные объекты.

2. Изменение указателя, который хранит адрес выделенной области памяти. Как уже упоминалось выше, в выделенной области хранятся данные об объекте - его размер. При удалении free получает эту информацию. Однако, если мы изменили указатель, то удаление приведёт к ошибке, например

Таким образом, если указатель хранит адрес, то его не нужно изменять. Для работы лучше создать дополнительную переменную указатель, с которой работать дальше.

3. Использование освобождённой области. Почему это работает в си, описано выше. Эта ошибка выливается в другую – так называемые висячие указатели (dangling pointers или wild pointers). Вы удаляете объект, но при этом забываете изменить значение указателя на NULL. В итоге, он хранит адрес области памяти, которой уже нельзя воспользоваться, при этом проверить, валидная эта область или нет, у нас нет возможности.

Эта программа отработает и выведет мусор, или не мусор, или не выведет. Поведение не определено.

Если же мы напишем

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

4. Освобождение освобождённой памяти. Пример

Здесь дважды вызывается free для переменной a. При этом, переменная a продолжает хранить адрес, который может далее быть передан кому-нибудь для использования. Решение здесь такое же как и раньше - обнулить указатель явно после удаления:

5. Одновременная работа с двумя указателями на одну область памяти. Пусть, например, у нас два указателя p1 и p2. Если под первый указатель была выделена память, то второй указатель может запросто скомпрометировать эту область:

Рассмотрим код ещё раз.

Теперь оба указателя хранят один адрес.

А вот здесь происходит непредвиденное. Мы решили выделить под p2 новый участок памяти. realloc гарантирует сохранение контента, но вот сам указатель p1 может перестать быть валидным. Есть разные ситуации. Во-первых, вызов malloc мог выделить много памяти, часть которой не используется. После вызова ничего не поменяется и p1 продолжит оставаться валидным. Если же потребовалось перемещение объекта, то p1 может указывать на невалидный адрес (именно это с большой вероятностью и произойдёт в нашем случае). Тогда p1 выведет мусор (или же произойдёт ошибка, если p1 полезет в недоступную память), в то время как p2 выведет старое содержимое p1. В этом случае поведение не определено.

Два указателя на одну область памяти это вообще-то не ошибка. Бывают ситуации, когда без них не обойтись. Но это очередное минное поле для программиста.

Различные аргументы realloc и malloc.

При вызове функции malloc, realloc и calloc с нулевым размером поведение не определено. Это значит, что может быть возвращён как NULL, так и реальный адрес. Им можно пользоваться, но к нему нельзя применять операцию разадресации.
Вызов realloc(NULL, size_t) эквиваленте вызову malloc(size_t).
Однако, вызов realloc(NULL, 0) не эквивалентен вызову malloc(0) :) Понимайте это, как хотите.

Примеры

1. Простое скользящее среднее равно среднему арифметическому функции за период n. Пусть у нас имеется ряд измерений значения функции. Часто эти измерения из-за погрешности "плавают" или на них присутствуют высокочастотные колебания. Мы хотим сгладить ряд, для того, чтобы избавиться от этих помех, или для того, чтобы выявить общий тренд. Самый простой способ: взять n элементов ряда и получить их среднее арифметическое. n в данном случае - это период простого скользящего среднего. Так как мы берём n элементов для нахождения среднего, то в результирующем массиве будет на n чисел меньше.

Это простой пример. Большая его часть связана со считыванием данных, вычисление среднего всего в девяти строчках.

2. Сортировка двумерного массива. Самый простой способ сортировки - перевести двумерный массив MxN в одномерный размером M*N, после чего отсортировать одномерный массив, а затем заполнить двумерный массив отсортированными данными. Чтобы не тратить место под новый массив, мы поступим по-другому: если проходить по всем элементам массива k от 0 до M*N, то индексы текущего элемента можно найти следующим образом:
j = k / N;
i = k - j*M;
Заполним массив случайными числами и отсортируем

3. Бином Ньютона. Создадим треугольную матрицу и заполним биномиальными коэффициентами

Если Вы желаете изучать этот материал с преподавателем, советую обратиться к репетитору по информатике

email

Всё ещё не понятно? – пиши вопросы на ящик

Динамическое и статическое выделение памяти. Преимущества и недостатки. Выделение памяти для одиночных переменных операторами 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 ).

В этом руководстве мы научимся эффективно управлять памятью в C++ с помощью операций создания и удаления на примерах. С++ позволяет нам выделять память для переменной или массива во время выполнения. Это известно как распределение динамической памяти.

В других языках программирования, таких как Java и Python, компилятор автоматически управляет памятью, выделенной для переменных. Но в C++ дело обстоит иначе. В С++ нам нужно вручную освободить динамически выделенную память после того, как мы перестали использовать переменную.

Мы можем динамически выделять, а затем освобождать память, используя операторы new и delete соответственно.

Оператор new

Оператор new выделяет память для переменной. Например:

Здесь мы динамически выделяем память для переменной типа int с помощью оператора new.

Обратите внимание, что мы использовали указатель pointVar для динамического распределения памяти. Это связано с тем, что оператор new возвращает адрес ячейки памяти.

В случае массива оператор new возвращает адрес первого элемента массива.

Из приведенного выше примера мы видим, что синтаксис использования оператора new следующий:

Оператор delete

Когда нам больше не нужно использовать переменную, которую мы объявили динамически, мы можем освободить память, занимаемую переменной.

Для этого используется оператор delete. Он возвращает память операционной системе, это и называется освобождением памяти.

Здесь мы динамически выделяем память для переменной типа int с помощью указателя pointVar .

После печати содержимого pointVar мы освободили память с помощью delete.

Примечание. Если программа использует большой объем нежелательной памяти с помощью new, система может дать сбой, поскольку для операционной системы не будет памяти. В этом случае оператор delete может помочь системе.

Пример 1: распределение динамической памяти

В этой программе мы динамически выделяли память для двух переменных типа int и float. После присвоения им значений и их печати, мы, наконец, освобождаем память с помощью кода:

Примечание. Динамическое выделение памяти в С++ может повысить эффективность управления памятью.

Особенно для массивов, где часто мы не знаем размер массива до времени выполнения.

Пример 2: для массивов

В этой программе мы попросили пользователя ввести количество студентов и сохранить его в переменной num .

Затем мы динамически выделили память для массива с плавающей запятой с помощью new .

Мы вводим данные в массив (а позже распечатываем их), используя обозначение указателя.

После того, как массив нам больше не нужен, мы освобождаем память массива с помощью кода delete [] ptr.

Обратите внимание на использование квадратных скобок [] после удаления. Мы используем их, чтобы обозначить, что освобождение памяти происходит в массиве.

Пример 3: для объектов

В этой программе мы создали класс Student с частной переменной age .

Мы инициализировали Age 12 в конструкторе по умолчанию Student() и распечатали его значение с помощью функции getAge().

В main() мы создали объект Student с помощью оператора new и используем указатель ptr, чтобы указать на его адрес.

В момент создания объекта конструктор Student() инициализирует Age равным 12.

Затем мы вызываем функцию getAge(), используя код:

Обратите внимание на оператор стрелки ->. Он используется для доступа к членам класса с помощью указателей.

Читайте также: