Massdomain.ru

Хостинг и домены
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Динамическое распределение памяти в си

Начать взаимодействие с динамической памятью на языке программирования С можно при помощи необходимых функций, распределяющих и выделяющих динамическую память, такие как calloc, malloc, free. Для того, чтобы их применять, нужно подключить библиотеку malloc.h.

Язык С++ в свою очередь пользуется более новыми методами использования динамической памяти для конкретных целей — операциями new и delete.

  • new выполняет задачу выделения памяти;
  • delete выполняет освобождение памяти.

Форматы, в виде которых может использоваться оператор new.

  • new тип; — при взаимодействии с аргументами;
  • new тип[размер]; — при взаимодействии с массивами.

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

int *ptr_i;
double *ptr_d;
struct person *human;

ptr_i = new int;
ptr_d = new double[10];
human = new struct person;

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

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

  • delete указатель; — взаимодействует с элементом;
  • delete [] указатель; — взаимодействует с массивами.

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

Операция delete работает только тогда, когда память выделена оператором new.

Создадим произвольный динамический массив.

Результат работы программы.

Заключение

Базовым адресом распределяемого динамического массива считается указатель dan, а число элементов этого массива — size. При помощи операции delete память, распределенная при работе оператора new, освобождается.

Русские Блоги

Есть два способа открыть пространство памяти в C:
1. Статически открыть память: например:

Характеристики этого открытого пространства памяти
Открытая память Откройте в стеке из Фиксированный размер Да, если a равно 4 байта, массив b равен 40 байтов, и длина массива должна быть указана при объявлении, поскольку память массива выделяется во время компиляции. Если мы хотим определить массив во время выполнения программы Размер, метод статического открытия пространства памяти не работает, например:

Запись таким образом вызовет ошибку во время компиляции. Компилятор напомнит, что [] должно быть константным выражением. При определении массивов в C, например, могут использоваться следующие типы:

Следует отметить, что const int n = 10 в C; n не может определить массив как длину массива, но в C ++,
Но наша потребность в разработке пространства часто этим не ограничивается. Наиболее распространенное определение массива — это когда размер массива известен во время работы программы. Статическая разработка уже бессильна. Конечно, есть статическая разработка. Должно быть динамическое развитие, далее мы рассмотрим динамическое развитие пространства памяти.

2. Динамически открывать память:
Для динамического открытия пространства в C необходимы три функции:
malloc(), calloc(), realloc() , Эти три функции Применить к куче Пространство памяти.
Пространство памяти, применяемое в куче, не будет таким же, как локальные переменные, хранящиеся в стеке. Вызов функции автоматически освободит память после вызова функции. Если нам нужно освободить ее вручную, нам нужно free() Функция для завершения.
Давайте посмотрим на соответствующие функции, способы использования, различия и связи этих функций.

1.malloc()

void * malloc(size_t size)

Читайте так же:
Монитор совмещенный с системным блоком

1). malloc() Функция будет отвал Подать заявку на ломтик непрерывный из имеющийся Пространство памяти
2). Если приложение успешно, возвращается указатель на это пространство памяти. Если приложение не удается, оно возвращается. NULL, Итак, мы используем malloc() После того, как функция открывает динамическую память, Обязательно определите, является ли возвращаемое значение функции NULL.
3). Тип возврата void* Введите, malloc() Функция не знает непрерывного развития size Какой тип данных хранится в каждом байте, поэтому мы должны решить для себя, метод malloc() Перед усилением системы она преобразуется в нужный нам тип, такой как: (int*)malloc(sizeof(int)*n).
4). если size 0, это поведение не определено и могут возникнуть неизвестные ошибки, в зависимости от компилятора

Как это использовать, например.

В настоящее время это эквивалентно созданию массива p (n). Это значение n вводится пользователем во время работы программы.

2.free()

void free(void* ptr)
Объем памяти, запрошенный в куче, не будет таким же, как у локальных переменных, хранящихся в стеке. Память будет автоматически освобождена после вызова функции. Если мы не освободим ее вручную, она не будет освобождена до конца программы. , Это может привести к Утечка памяти, То есть данные в этой части памяти в куче больше не используются, но они занимают это пространство (в общепринятых терминах это занимает яму, а не дерьмо), поэтому, когда динамическая память, к которой мы обращались, больше не используется, она должна быть Выпуск вовремя.

1). если ptr Не указание на пространство памяти, выделенное с помощью функции динамического выделения памяти, приводит к неопределенному поведению.
2). если ptr Нулевой указатель, функция ничего не делает.
3). Эта функция не меняется ptr Значение само по себе, поэтому оно по-прежнему указывает на то же (теперь недопустимое) местоположение (память)
4). в free() После того, как функция должна снова очистить ptr, то есть ptr = NULL; Если нет ptr Если оставить пустым, если последующие процедуры пройдут снова ptr Получит доступ к памяти, которая была освобождена как недействительная или была переработана. Чтобы обеспечить надежность программы, мы обычно пишем ptr = NULL; .

Примечание: free() Не удается повторно освободить часть памяти, например:

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

free() Конкретное использование, например:

3.calloc()

void * calloc(size_t num,size_t size)
и malloc() Разница между функциями заключается в том, что calloc() Функция будет в До обратного адреса Запрашиваемая память Каждый байт инициализируется в 0 .

1). calloc() Функции функции распределяются динамически num Размер (длина байта) size Пространство памяти.
2). Если приложение успешно, возвращается указатель на это пространство памяти. Если приложение не удается, оно возвращается. NULL, Итак, мы используем calloc() После того, как функция открывает динамическую память, Обязательно определите, является ли возвращаемое значение функции NULL.
3). Тип возврата void* Введите, calloc() Функция хоть назначена num более size Размер памяти, но до сих пор не знаю, какой тип данных хранится, поэтому нам нужно решить для себя, метод calloc() Перед усилением системы она преобразуется в нужный нам тип, такой как: (int*)calloc(num, sizeof(int) ).
4). если size и num Один или оба равны 0, это поведение не определено, будут возникать неизвестные ошибки, в зависимости от компилятора

Итак, как мы инициализируем содержимое запрошенного пространства памяти, тогда мы можем легко использовать функцию calloc для выполнения этого требования.
Например:

Читайте так же:
Внешний жесткий диск пищит и не определяется

4.realloc()

void * realloc(void * ptr,size_t size)

realloc() Функции делают динамическое управление памятью более гибким. Динамическое выделение объема памяти во время выполнения программы. Если выделение слишком велико, это приведет к потере места. Если оно слишком мало, его может все еще быть недостаточно. Для разумного использования памяти мы будем Сделайте гибкие настройки размера памяти. что realloc() Функция может регулировать размер динамически открытой памяти (большой или небольшой).

1). ptr Для адресов памяти, которые должны быть скорректированы
2) . size Чтобы настроить размер (в байтах)
3). Если корректировка прошла успешно, возвращаемое значение — это начальная позиция измененной памяти (то есть указатель на скорректированную память), ptr Если произойдет сбой (когда нет памяти для выделения, он обычно не появляется), затем верните NULL , Так что все равно придется очистить возвращаемое значение
4). если ptr Нулевой указатель, то malloc() Функции работают одинаково

Примечание: есть два случая, когда функция realloc () расширяет пространство памяти
1). ptr За указанной памятью достаточно места для расширения, как показано на рисунке:

2). ptr Недостаточно места за указанной памятью для расширения, как показано на рисунке:

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

5. Резюме

1). malloc() и calloc() Использование функции такое же, разница только calloc() Инициализируйте каждый байт запрошенной памяти на 0

2). malloc() , calloc(), realloc() Когда запрошенная память больше не используется, она должна использоваться free() Отпустите, иначе это приведет к утечке памяти

3). p = realloc(ptr, size) Когда возвращаемое значение функции не пустое, нет необходимости писать при освобождении памяти free(ptr) И просто напиши free(p)

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

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

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

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

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

Аллокатор

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

Аллокатор — это часть программы, которая запрашивает память большими кусками напрямую у ОС через системные вызовы (в POSIX-совместимых ОС это mmap для выделения памяти и unmap — для освобождения), затем по частям отдаёт эту память приложению (в Си это могут быть функции malloc() / free() ). Такой подход увеличивает производительность, но может вызвать фрагментацию памяти при длительной работе программы.

malloc() / free() и mmap / unmap — это не одно и то же. Первый является простейшим аллокатором в libc , второй является системным вызовом. В большинстве языков можно использовать только аллокатор по умолчанию, но в языках с более низкоуровневой моделью памяти можно использовать и другие аллокаторы.

Читайте так же:
Жесткий диск с защитой данных

Например, boost::pool аллокаторы, созданные для оптимальной работы с контейнерами ( boost::pool_allocator для линейных ( std::vector ), boost::fast_pool_allocator для нелинейных ( std::map, std::list )). Или аллокатор jemalloc, оптимизированный для решения проблем фрагментации и утилизации ресурсов CPU в многопоточных программах. Более подробно о jemalloc можно узнать из доклада с конференции C++ Russia 2018.

Динамическая память

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

Операторы new и new[ ]

Динамическая память выделяется оператором new, за которым следует спецификатор типа данных и, если требуется последовательность из более, чем одного элемента, их количество в квадратных скобках [ ]. Он возвращает указатель на начало блока выделенной памяти. Его синтаксис:

pointer = new type
pointer = new type [число_элементов]

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

В этом случае система динамически выделяет пространство для пяти элементов типа int и возвращает указатель на первый элемент последовательности, который присваивается переменной foo (указатель). Следовательно, foo сейчас указывает на валидный блок памяти с пространством для пяти элементов типа int.

Здесь foo — это указатель, и таким образом, доступ к первому элементу foo можно получить либо выражением foo[0], либо выражением *foo ( они равнозначны). Доступ ко второму элементу можно получить с помощью foo[1] или *( foo+1), и так далее.

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

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

C++ предоставляет два стандартных механизма для проверки успешности выделения памяти:

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

Другой метод известен как nothrow. В этом случае вместо выбрасывания исключения или выхода из программы, new возвращает null pointer, и программа продолжает нормально выполняться (на практике оказывается, что исключение все равно может выбрасываться — прим. переводчика).

Этот метод может быть описан путем использования специального объекта nothrow, определенного в заголовочном файле <new>, как аргумента для new:

Читайте так же:
Материнская плата asrock h87 pro4

Этот метод обычно создает менее эффективный код, чем перехват исключений, потому что предусматривает явную проверку значения указателя после каждого выделения памяти. Следовательно, механизм исключений обычно предпочтительнее хотя бы для критических участков кода. Большинство примеров кода будут использовать механизм nothrow ввиду его простоты.

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

В этом примере выделяется память по адресу ptr для хранения одного объекта типа A, затем, при помощи специального синтаксиса оператора new, объект pobj типа A размещается по адресу ptr. Объекты, размещенные в памяти подобным образом, требуют уничтожения путем явного вызова деструктора объекта. После того, как первый объект уничтожен, выделенная память все еще доступна для дальнейшего использования. Далее таким же образом и по тому же адресу размещается объект pobj2, после чего он также уничтожается аналогичным образом. Перед выходом из программы, выделенная память освобождается вызовом оператора delete[] (см. следующий параграф). Можно заметить, что память выделяется и освобождается единожды, однако имеется возможность повторного использования участка памяти разными объектами, что подтверждается выводом этой программы:

ctor: 1
dtor: 1
ctor: 2
dtor: 2

Операторы delete и delete[ ]

В большинстве случаев, динамически выделенная память нужна в течение определенного периода времени. В тот момент, когда память больше не нужна, она может быть освобождена для того, чтобы она стала доступной для новых запросов. Для этой цели существует оператор delete[ ]:

Первое выражение освобождает память, выделенную при мощи new для одного элемента. Второе выражение освобождает память, выделенную для массива элементов с использованием new[ ].

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

Заметьте, что значение в скобках оператора new это переменное значение, введенное пользователем( i), а не константное выражение:

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

Считается хорошей практикой всегда обрабатывать ошибки при выделении памяти обработкой соответствующего исключения или проверкой значения указателя.

Динамическая память в C

C++ включает операторы new и delete для динамического выделения памяти, однако это не относится к языку C. Вместо них используются библиотечные функции malloc, calloc, realloc и free, определенные в <cstdlib> или <stdlib.h> в языке C. Эти функции также доступны и в C++ и могут быть использованы для выделения и освобождения памяти.

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

Выделение памяти

Для выделения и освобождения динамической памяти “из кучи” у нас есть две функции: malloc() и free() соответственно. Также есть операторы new и delete , которые делают то же самое. При выделении памяти мы получаем адрес на первый байт выделенной области, поэтому рекомендую почитать урок про адреса и указатели.

  • malloc(количество) – выделяет количество байт динамической памяти (из кучи) и возвращает адрес на первый байт выделенной области. Если свободной памяти недостаточно для выделения – возвращает “нулевой указатель” – NULL .
  • free(ptr) – освобождает память, на которую указывает ptr , назад в кучу. Адрес мы получаем в результате работы malloc() при выделении. Освободить можно только память, выделенную при помощи функций malloc() , realloc() или calloc() . В выделяемой области хранится размер этой области (+2 байта), и при освобождении функция free знает, какой размер освобождать.
  • new и delete – технически то же самое, разница в применении (см. пример ниже)
Читайте так же:
Зарядное устройство для автомобильного аккумулятора из бесперебойника

Рассмотрим пример с выделением и освобождением памяти при помощи malloc/free и new/delete. Примеры абсолютно одинаковые с точки зрения происходящего, отличаются только функциями: malloc/free

new/delete

Таким образом мы выделили себе память, с этой памятью можем взаимодействовать (как с обычной переменной), и потом её освободить. Напомню, что освобождать крайне желательно в обратном порядке, чтобы память освобождалась последовательно, не оставляя дыр. Есть ещё две функции: calloc() и realloc() :

  • calloc(количество, размер) – выделяет память под количество элементов с размером размер каждый (в байтах). Тот же malloc, но чуть удобнее использовать: в примере выше мы умножали, чтобы получить нужное количество байт для хранения int malloc(20 * sizeof(int)) , а можно было вызвать calloc(20, sizeof(int)); – заменив знак умножения на запятую =)
  • realloc(ptr, размер) – изменяет величину выделенной памяти, на которую указывает ptr, на новую величину, задаваемую параметром размер. Величина размер задается в байтах и может быть больше или меньше оригинала. Возвращается указатель на блок памяти, поскольку может возникнуть необходимость переместить блок при возрастании его размера. В таком случае содержимое старого блока копируется в новый блок и информация не теряется.

Распределение динамической памяти для объектов

Мы также можем динамически размещать объекты.

Как мы знаем, Constructor — это специальная функция-член класса, используемая для инициализации объекта, а Destructor — также функция-член класса, которая вызывается всякий раз, когда объект выходит за пределы области видимости.

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

  • Когда локальный объект выходит за рамки
  • Для глобального объекта, когда оператор применяется к указателю на объект класса

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

Давайте посмотрим на пример массива объектов.

Вывод:

Пояснение:

Конструктор будет вызываться три раза, поскольку мы выделяем память трем объектам класса Random. Деструктор также будет вызываться трижды во время каждого из этих объектов. ‘Random * a = new Random [3]’ этот оператор отвечает за динамическое распределение памяти нашего объекта.

На этом мы подошли к концу статьи о динамическом распределении памяти C ++. Если вы хотите узнать больше, ознакомьтесь с от Edureka, надежной компании онлайн-обучения. Курс обучения и сертификации по Java J2EE и SOA от Edureka разработан, чтобы обучить вас базовым и продвинутым концепциям Java, а также различным средам Java, таким как Hibernate и Spring.

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

голоса
Рейтинг статьи
Ссылка на основную публикацию
Adblock
detector