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

Обновлено: 04.07.2024

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

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

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

указатель = новый тип
указатель = новый тип [количество_элементов]

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

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


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

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

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

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

Во-первых, обработка исключений. При использовании этого метода при сбое выделения создается исключение типа bad_alloc. Исключения — это мощная функция C++, описанная далее в этих руководствах. Но пока вы должны знать, что если это исключение выдается и оно не обрабатывается определенным обработчиком, выполнение программы прекращается.

Этот метод исключения является методом, используемым по умолчанию new , и именно он используется в объявлении, например:

Другой метод известен как nothrow , и при его использовании происходит следующее: при сбое выделения памяти вместо создания исключения bad_alloc или завершения программы указатель, возвращаемый new, является нулевым указателем< /i>, и программа продолжит свое выполнение в обычном режиме.

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

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

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

Операторы удаления и удаления[]

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

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

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

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

Всегда существует вероятность того, что пользователь введет значение i настолько велико, что система не сможет выделить для него достаточно памяти. Например, когда я попытался дать значение 1 миллиард на вопрос «Сколько чисел», моя система не смогла выделить столько памяти для программы, и я получил текстовое сообщение, которое мы подготовили для этого случая (Ошибка: память может не выделяться ).

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

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

C++ объединяет операторы new и delete для выделения динамической памяти. Но они не были доступны на языке C; вместо этого он использовал библиотечное решение с функциями malloc, calloc, realloc и free, определенными в заголовке (известном как в C). Эти функции также доступны в C++ и могут использоваться для выделения и освобождения динамической памяти.

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

Как вы знаете, массив — это набор фиксированного числа значений. После объявления размера массива его нельзя изменить.

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

Для динамического выделения памяти используются библиотечные функции malloc(), calloc(), realloc() и free(). Эти функции определены в заголовочном файле.

C malloc()

Имя "malloc" означает выделение памяти.

Функция malloc() резервирует блок памяти с указанным количеством байтов. И он возвращает указатель типа void, который можно преобразовать в указатели любой формы.

Синтаксис malloc()

Пример

Приведенный выше оператор выделяет 400 байт памяти. Это потому, что размер float составляет 4 байта. А указатель ptr содержит адрес первого байта в выделенной памяти.

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

C calloc()

Название "calloc" означает непрерывное выделение.

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

Синтаксис calloc()

Пример:

Приведенный выше оператор выделяет непрерывное пространство в памяти для 25 элементов типа float .

С бесплатно()

Динамически выделяемая память, созданная с помощью calloc() или malloc(), не освобождается сама по себе. Вы должны явно использовать free() для освобождения места.

Синтаксис функции free()

Этот оператор освобождает место, выделенное в памяти, на которую указывает ptr .

Пример 1: malloc() и free()

Вывод

Здесь мы динамически выделили память для n числа int .

Пример 2: calloc() и free()

Вывод

C realloc()

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

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

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

Пространства памяти C/C++

Возможно, полезно представить, что память данных в C и C++ разделена на три отдельных пространства:

Статическая память. Здесь находятся переменные, которые определены вне функций. Ключевое слово static обычно не влияет на расположение таких переменных; он определяет их область действия как локальную по отношению к текущему модулю. Переменные, определенные внутри функции, явно объявленные статическими, также хранятся в статической памяти. Обычно статическая память располагается в начале области ОЗУ. Фактическое распределение адресов по переменным выполняется встроенным набором инструментов для разработки программного обеспечения: сотрудничество между компилятором и компоновщиком. Обычно секции программы используются для управления размещением, но более продвинутые методы, такие как мелкозернистое распределение, дают больше контроля.Обычно вся оставшаяся память, которая не используется для статического хранения, используется для создания области динамического хранения, в которой размещаются два других пространства памяти.

Автоматические переменные. Переменные, определенные внутри функции, которые не объявлены статическими, являются автоматическими. Для явного объявления такой переменной есть ключевое слово auto, но оно почти никогда не используется. Автоматические переменные (и параметры функций) обычно хранятся в стеке. Стек обычно размещается с помощью компоновщика. Конец области динамического хранения обычно используется для стека. Оптимизация компилятора может привести к тому, что переменные будут храниться в регистрах часть или все время их существования; это также может быть предложено с помощью регистра ключевого слова.

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

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

В C динамическая память выделяется из кучи с помощью некоторых стандартных библиотечных функций. Двумя ключевыми функциями динамической памяти являются malloc() и free().

Функция malloc() принимает единственный параметр — размер запрошенной области памяти в байтах. Он возвращает указатель на выделенную память. Если выделение не удается, возвращается NULL. Прототип стандартной библиотечной функции выглядит следующим образом:

void *malloc(size_t size);

Функция free() берет указатель, возвращенный функцией malloc(), и освобождает память. Никаких признаков успеха или неудачи не возвращается. Прототип функции выглядит следующим образом:

void free(void *pointer);

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

целый_массив[10];
мой_массив[3] = 99;

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

инт *указатель;
указатель = malloc(10 * sizeof(int));
*(указатель+3) = 99;

Синтаксис разыменования указателя трудно читать, поэтому можно использовать обычный синтаксис ссылки на массив, поскольку [ и ] — это просто операторы:

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

свободно(указатель);
указатель = NULL;

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

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

Есть еще два варианта функции malloc(): calloc() и realloc().

Функция calloc() в основном выполняет ту же работу, что и malloc(), за исключением того, что она принимает два параметра — количество элементов массива и размер каждого элемента — вместо одного параметра (который является произведением этих двух значения). Выделенная память также инициализируется нулями. Вот прототип:

void *calloc(size_t elements, size_t elementSize);

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

void *realloc(void *pointer, size_t size);

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

Управление динамической памятью в C++ во многом похоже на C. Хотя библиотечные функции, скорее всего, будут доступны, в C++ есть два дополнительных оператора — new и delete — которые позволяют писать код более четко, лаконично и гибко, с меньшей вероятностью ошибок. Новый оператор можно использовать тремя способами:

p_var = новое имя типа;
p_var = новый тип(инициализатор);
p_array = новый тип [размер];

В первых двух случаях выделяется место для одного объекта; второй включает инициализацию. Третий случай — это механизм выделения места под массив объектов.

Оператор удаления можно вызвать двумя способами:

удалить p_var;
удалить[] p_array;

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

Не существует оператора, обеспечивающего функциональность функции C realloc().

Вот код для динамического выделения массива и инициализации четвертого элемента:

int* указатель;
указатель = новый int[10];
указатель[3] = 99;

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

удалить[] указатель;
указатель = NULL;

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

Вопросы и проблемы

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

Существует ряд проблем с динамическим выделением памяти в системе реального времени. Стандартные библиотечные функции (malloc() и free()) обычно не допускают повторного входа, что было бы проблематично в многопоточном приложении. Если исходный код доступен, это можно легко исправить, заблокировав ресурсы с помощью средств RTOS (например, семафора). Более сложная проблема связана с производительностью malloc(). Его поведение непредсказуемо, так как время, необходимое для выделения памяти, чрезвычайно изменчиво. Такое недетерминированное поведение недопустимо в системах реального времени.

Без особой осторожности легко ввести утечки памяти в код приложения, реализованный с помощью malloc() и free(). Это вызвано тем, что память выделяется и никогда не освобождается. Такие ошибки, как правило, вызывают постепенное снижение производительности и, в конечном итоге, сбой. Этот тип ошибки может быть очень трудно обнаружить.

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

Фрагментация памяти

Лучший способ понять фрагментацию памяти — посмотреть на пример. В этом примере предполагается, что существует куча размером 10 КБ. Во-первых, запрашивается область размером 3 КБ, таким образом:

Затем запрашивается еще 4 КБ:

3 КБ памяти теперь свободно.

Некоторое время спустя первое выделение памяти, на которое указывает p1, освобождается:

При этом остается 6 КБ свободной памяти, разбитой на два фрагмента по 3 КБ. Выдается дополнительный запрос на выделение 4K:

Это приводит к сбою — в p1 возвращается NULL — потому что, хотя доступно 6 КБ памяти, нет доступного непрерывного блока размером 4 КБ. Это фрагментация памяти.

Память с RTOS

Операционная система реального времени может предоставлять службу, которая фактически является реентерабельной формой malloc(). Однако маловероятно, что это средство будет детерминированным.

Обычно предоставляются средства управления памятью, совместимые с требованиями реального времени, т. е. детерминированные. Чаще всего это схема, которая выделяет блоки или «разделы» памяти под управлением ОС.

Блокирование/разделение памяти

Обычно выделение блочной памяти выполняется с использованием «пула разделов», который определяется статически или динамически и настраивается таким образом, чтобы содержать определенное количество блоков определенного фиксированного размера. Для ОС Nucleus вызов API для определения пула разделов имеет следующий прототип:

STATUS
NU_Create_Partition_Pool (NU_PAR TITION_POOL *pool, CHAR *name, VOID *start_address, UNSIGNED pool_size, UNSIGNED partition_size, OPTION suspend_type);

Наиболее наглядно это можно понять на примере:

При этом создается пул разделов с дескриптором MyPool, содержащий 2000 байт памяти, заполненный разделами размером 40 байт (т. е. существует 50 разделов). Пул расположен по адресу 0xB000. Пул настроен таким образом, что, если задача пытается выделить блок, когда его нет в наличии, и запрашивает приостановку при вызове API выделения, приостановленные задачи будут разбужены в порядке «первым поступил — первым вышел». . Другим вариантом был бы порядок приоритета задач.

Для запроса выделения раздела доступен другой вызов API. Вот пример использования ОС Nucleus:

Это запрашивает выделение раздела из MyPool. В случае успеха указатель на выделенный блок возвращается в ptr. Если памяти нет, задача приостанавливается, так как указано NU_SUSPEND; другими вариантами, которые могли быть выбраны, были бы приостановка с тайм-аутом или просто возврат с ошибкой.

Когда раздел больше не нужен, его можно освободить следующим образом:

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

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

Обнаружение утечки памяти

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

Решения для оперативной памяти

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

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

Можно использовать выделение памяти разделов для надежной и детерминированной реализации malloc(). Идея состоит в том, чтобы определить серию пулов разделов с размерами блоков в геометрической прогрессии; например 32, 64, 128, 256 байт. Функция malloc() может быть написана для детерминированного выбора правильного пула, чтобы обеспечить достаточно места для данного запроса на выделение. В этом подходе используется детерминированное поведение вызова API выделения разделов, надежная обработка ошибок (например, приостановка задачи) и иммунитет к фрагментации, обеспечиваемый блочной памятью.

Выводы

C и C++ используют память различными способами, как статическими, так и динамическими. Динамическая память включает стек и кучу.

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

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

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

void *calloc(int num, int size);

Эта функция выделяет массив из num элементов, размер каждого из которых в байтах будет равен size.

void free(void *address);

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

void *malloc(size_t size);

Эта функция выделяет массив байтов num и оставляет их неинициализированными.

void *realloc(void *address, int newsize);

Эта функция перераспределяет память, увеличивая ее до нового размера.

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

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

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

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

Та же программа может быть написана с помощью calloc(); единственное, что вам нужно заменить malloc на calloc следующим образом —

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

Изменение размера и освобождение памяти

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

Кроме того, вы можете увеличить или уменьшить размер выделенного блока памяти, вызвав функцию realloc(). Давайте еще раз проверим приведенную выше программу и воспользуемся функциями realloc() и free() —

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

Вы можете попробовать приведенный выше пример без перераспределения дополнительной памяти, и функция strcat() выдаст ошибку из-за отсутствия доступной памяти в описании.

Прежде чем вы изучите выделение динамической памяти C, давайте разберемся:

Как работает управление памятью в C?

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

Например, переменная с плавающей точкой при объявлении обычно занимает 4 байта (в зависимости от платформы).Мы можем проверить эту информацию, используя оператор sizeof, как показано в примере ниже

Вывод будет таким:

Также массив заданного размера размещается в смежных блоках памяти, каждый блок имеет размер одного элемента:

Как известно, при объявлении базового типа данных или массива память управляется автоматически. Однако существует процесс выделения памяти в C, который позволит вам реализовать программу, в которой размер массива не определен до тех пор, пока вы не запустите свою программу (время выполнения). Этот процесс называется «Динамическое выделение памяти».

В этом уроке вы узнаете-

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

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

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

В библиотеке есть функции, отвечающие за динамическое управление памятью.

Функция Назначение
malloc() Выделяет память запрошенного размера и возвращает указатель на первый байт
выделенного пространства.
calloc() Выделяет место для элементов массива. Инициализирует элементы нулем и возвращает указатель на память.
realloc() Используется для изменения размера ранее выделенного пространства памяти.
Free() Освобождает или очищает ранее выделенное пространство памяти.
< /p>

Давайте обсудим вышеуказанные функции и их применение

функция malloc() в C

Функция C malloc() означает выделение памяти. Это функция, которая используется для динамического выделения блока памяти. Он резервирует пространство памяти указанного размера и возвращает нулевой указатель, указывающий на ячейку памяти. Возвращаемый указатель обычно имеет тип void. Это означает, что мы можем присвоить функцию C malloc() любому указателю.

Синтаксис функции malloc():

  • ptr — это указатель cast_type.
  • Функция malloc() языка C возвращает указатель на выделенную память размером byte_size.

Пример функции malloc():

При успешном выполнении этого оператора резервируется 50 байт памяти. Адрес первого байта зарезервированного пространства присваивается указателю ptr типа int.

Вывод:

C malloc() FunctionКак сохранить изображение без фона в coreldraw   

  • Как сделать qr-код для инстаграма
  •   
  • Что такое кодек av1
  •   
  • Обзор планшета blackview tab 8
  •   
  • Как открыть файл в блокноте в filezilla