Управление памятью в c

Обновлено: 03.07.2024

В 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++ используют память различными способами, как статическими, так и динамическими. Динамическая память включает стек и кучу.

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

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

До сих пор вы, вероятно, мало задумывались о том, как работает программа или как она работает. Что ж, большинству из нас известно, что программа — это файл, который загружается в память в какой-то неопределенный момент. Итак, учитывая приведенный ниже код, где хранятся a , b и c?

a , b и c хранятся в стеке, что является причудливым словом для небольшой области памяти, где временные переменные добавляются и удаляются в специальном способ. Все добавляется и удаляется, начиная сверху, как стопка карт. Обычно вы берете карты сверху, а иногда можете положить их сверху; эти операции со стеком называются pop и push соответственно. Таким образом, мы можем использовать дедуктивное рассуждение, чтобы сказать, что в стек помещается a, за которым следуют b и c. Отличительной особенностью распределения памяти в стеке является простота использования. Память выделяется достаточно быстро с минимальными накладными расходами. Когда вы закончите использовать память, она автоматически освободится. Это означает, что нам не нужно управлять памятью.

Хотя это отличный способ выделения памяти, было упомянуто, что стек обычно небольшой, а это означает, что вы не хотите помещать в стек большие переменные; в основном большие массивы и большие структуры. Если вы не хотите тратить свое драгоценное пространство в стеке, где и как вы можете выделить большие объемы памяти? К счастью, для этого зарезервирована большая область памяти. Эта область называется кучей. По сути, куча — это очень большой пул памяти, некоторые его части могут использоваться или не использоваться. К счастью, операционная система берет на себя сложную задачу управления им; все, что нам нужно знать, это как выделить память и освободить ее. К сожалению, если вы не освободите выделенную память, она останется выделенной до тех пор, пока вы этого не сделаете. Это может привести к утечке памяти, при которой вы потеряете расположение выделенной памяти и никогда не сможете ее освободить. Это может повлиять на производительность и, в конечном итоге, может привести к тому, что вашей программе не хватит памяти, что приведет к ее сбою. Нужно быть особенно осторожным при работе с динамическим выделением памяти.

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

Выделение памяти [ редактировать | изменить источник ]

Прежде чем мы начнем с некоторых примеров, важно знать, что все функции выделения памяти хранятся в заголовочном файле stdlib.h. Первая функция, с которой мы будем работать, это malloc, которая отвечает за выделение памяти. Эта функция принимает один параметр — размер выделенной памяти в size_t. size_t — это максимальное число, используемое для адресации памяти на компьютере, поэтому, например, на 32-разрядном компьютере size_t может выражать 4 ГБ.

Примечание. Фактический размер самого типа size_t будет составлять всего 4 байта на 32-разрядном компьютере, а не 4 ГиБ! size_t может, например, хранить адрес второго ГиБ в пределах своих 4 байтов. С рациональной точки зрения можно сказать, что size_t может адресовать любое место в памяти компьютера.

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

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

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

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

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

Предупреждение. Прежде чем вы начнете выделять память как сумасшедшую, вам нужно убедиться, что вы не выделяете «ничего». Это происходит, когда вы пытаетесь что-то вроде malloc(0) , которое не выделяет вам никакой памяти. Хуже того, он может вернуть нулевой указатель, но это не гарантируется. Это означает, что можно получить указатель на память, к которой не следует прикасаться, что, безусловно, поможет при сбое вашей программы.

Освобождение выделенной памяти [ edit | изменить источник ]

Теперь, когда вы выделили память, ваша программа будет хранить ее до тех пор, пока она существует. Это означает, что выделение больших блоков памяти может истощить ресурсы вашего компьютера. В какой-то момент вы захотите вернуть эту память. Бесплатная функция может помочь нам здесь. Он освобождает выделенную память, что позволяет компьютеру позже выделить больше. Использовать бесплатно просто; все, что требуется, это один параметр, указатель на выделенную память. Демонстрация приведена ниже.

Предупреждение. Высвобождение незанятого указателя вызовет неопределенное поведение. К счастью, free ничего не сделает, если вы попытаетесь освободить нулевой указатель. Никогда не пытайтесь освободить то, что никогда не выделялось изначально!

Изменение размера выделенной памяти [ edit | изменить источник ]

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

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

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

Выделение чистой памяти [ edit | изменить источник ]

Использование malloc для выделения памяти — это здорово, но у него все еще есть свои недостатки. malloc не беспокоится о состоянии выделенной памяти, а это означает, что каждый байт может иметь значение undefined. Это может быть проблематично с массивом, который мы использовали. При выделении массив может содержать неопределенные значения. Обычно, когда мы выделяем массив, мы хотим, чтобы все было равно нулю. К счастью, последняя функция, обсуждаемая в этом уроке, выделяет память и гарантирует, что вся память будет обнулена. Эта функция называется calloc и принимает два параметра: количество элементов, которые вы хотите выделить, и их размер. calloc демонстрируется в примере ниже.

Вывод должен быть:

Проблемы с управлением памятью [ редактировать | изменить источник ]

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

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

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

  1. Только не используйте их! Это легче сказать, чем сделать, особенно в больших приложениях.
  2. Установите висячий указатель на NULL . Сравнивая с NULL , вы теперь знаете, подходит ли указатель для использования.

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

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

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

Может ли кто-нибудь показать мне (с примерами кода) пример, когда вам нужно будет выполнить некоторое «управление памятью»?

12 ответов 12

Есть два места, где переменные могут быть помещены в память. Когда вы создаете такую ​​переменную:

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

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

Стек удобен тем, что он автоматический, но у него также есть два недостатка: (1) компилятору необходимо заранее знать, насколько велики переменные, и (2) пространство стека несколько ограничено. Например: в Windows при настройках по умолчанию для компоновщика Microsoft стек установлен на 1 МБ, и не весь он доступен для ваших переменных.

Если во время компиляции вы не знаете, насколько велик ваш массив, или если вам нужен большой массив или структура, вам нужен "план Б".

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

(Обратите внимание, что переменные в куче управляются не напрямую, а через указатели)

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

Что делает второй вариант "неприятным делом", так это то, что не всегда легко понять, когда переменная больше не нужна. Если вы забудете освободить переменную, когда она вам не нужна, ваша программа будет потреблять больше памяти, чем ей нужно. Такая ситуация называется «утечкой». «Утечка» памяти не может быть использована ни для чего, пока ваша программа не завершится и ОС не восстановит все свои ресурсы. Возможны еще более неприятные проблемы, если вы по ошибке освободите переменную кучи до того, как вы на самом деле закончите с ней работать.

(Я упустил многие детали, чтобы дать более простой, но, надеюсь, более точный ответ)

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

Вот пример. Предположим, у вас есть функция strdup(), дублирующая строку:

И вы называете это так:

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

Это не имеет большого значения для такого небольшого объема памяти, но рассмотрим случай:

Теперь вы израсходовали 11 ГБ памяти (возможно, больше, в зависимости от вашего менеджера памяти), и если у вас не произошел сбой, ваш процесс, вероятно, работает довольно медленно.

Чтобы исправить это, вам нужно вызвать free() для всего, что получено с помощью malloc() после того, как вы закончите его использовать:

Надеюсь, этот пример поможет!

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

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

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

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

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

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

Распределение кучи менее характерно для языка. По сути, это набор библиотечных вызовов, который предоставляет вам право собственности на блок памяти заданного размера до тех пор, пока вы не будете готовы его вернуть («освободить»). Звучит просто, но ассоциируется с несказанным горем программиста. Проблемы просты (освобождение одной и той же памяти дважды или не освобождение вообще [утечки памяти], недостаточное выделение памяти [переполнение буфера] и т. д.), но их трудно избежать и отладить. Строгий дисциплинированный подход абсолютно обязателен на практике, но, конечно, формулировка этого не требует.

Я хотел бы упомянуть еще один тип распределения памяти, который был проигнорирован другими сообщениями. Можно статически выделить переменные, объявив их вне какой-либо функции. Я думаю, что в целом этот тип распределения имеет плохую репутацию, потому что он используется глобальными переменными. Однако ничто не говорит о том, что единственный способ использовать память, выделенную таким образом, - это использовать недисциплинированную глобальную переменную в беспорядке спагетти-кода. Метод статического распределения можно использовать просто для того, чтобы избежать некоторых ловушек кучи и методов автоматического распределения. Некоторые программисты на 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().

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