Освобождение памяти c free
В классе создается структура и указатель на нее.
В конструкторе для структуры выделяется нужная память.
После использования класса нужно освободить память, выделенную для структуры.
Для операций с памятью используются malloc() и free() .
Как правильно освободить память, выделенную для структуры?
В конструкторе выделяется память для структуры:
Теперь нужно в деструкторе освободить память, выделенную для структуры.
Как это правильно сделать, используя функцию free() ?
проверяете указатель pData, а память освобождаете у pFitData. Странно. С другой стороны - нужно просто позвать free в противоположном порядке от malloc
free(pData) тоже нужно _ освободив память на которую указывает член и освободить память выделенная для объекта
@ARHovsepyan - внутри структуры есть указатель, который указывает на массив динамически выделенной памяти. Сначала нужно освободить эту память. Варианты типа free(pData) не работают
Подробнее выложите код, ошибка где-то происходит. Может-быть даже не из-за этого класса. По этому происходит неопределённое поведение.
calloc
Ф ункция calloc выделяет n объектов размером m и заполняет их нулями. Обычно она используется для выделения памяти под массивы. Синтаксис
Освобождение памяти с помощью free
Т еперь рассмотри, как происходит освобождение памяти. Переменная указатель хранит адрес области памяти, начиная с которого она может им пользоваться. Однако, она не хранит размера этой области. Откуда тогда функция free знает, сколько памяти необходимо освободить?
- 1. Можно создать карту, в которой будет храниться размер выделенного участка. Каждый раз при освобождении памяти компьютер будет обращаться к этим данным и получать нужную информацию.
- 2. Второе решение более распространено. Информация о размере хранится на куче до самих данных. Таким образом, при выделении памяти резервируется места больше и туда записывается информация о выделенном участке. При освобождении памяти функция free "подсматривает", сколько памяти необходимо удалить.
2 ответа 2
В С++ объекты создаются динамически при помощи new-выражений и разрушаются при помощи delete-выражений.
New-выражения и delete-выражения используют функции выделения динамической памяти operator new , operator new[] и функции освобождения operator delete , operator delete[] соответственно.
Реализация С++ предоставляет несколько глобальных функций выделения и освобождения. При этом они могут быть переопределены пользователем без нарушения ODR (но не более одного раза в программе).
Пользователь также может добавлять свои функции выделения и освобождения, в виде статических членов класса или свободных функций в глобальном пространстве имен. Такие пользовательские функции должны соблюдать семантику встроенных (не выделять уже выделенные адреса и т.п.).
Пример. типы, задающих операторы new и DELETE
В следующем примере показаны три типа, каждый из new которых задает операторы и delete . new_delete Класс использует глобальные new операторы и delete , malloc_free а класс использует функции вызываемой среды выполнения C и функции, а Alloc_Free класс использует среда выполнения с параллелизмом Alloc и Free .
Работа с двумерными и многомерными массивами
Д ля динамического создания двумерного массива сначала необходимо создать массив указателей, после чего каждому из элементов этого массива присвоить адрес нового массива.
Для удаления массива необходимо повторить операцию в обратном порядке - удалить сначала подмассивы, а потом и сам массив указателей.
- 1. Создавать массивы "неправильной формы", то есть массив строк, каждая из которых имеет свой размер.
- 2. Работать по отдельности с каждой строкой массива: освобождать память или изменять размер строки.
Создадим "треугольный" массив и заполним его значениями
Чтобы создать трёхмерный массив, по аналогии, необходимо сначала определить указатель на указатель на указатель, после чего выделить память под массив указателей на указатель, после чего проинициализировать каждый из массивов и т.д.
Полный пример кода
Полный пример приведен ниже.
В этом примере выводится следующий пример выходных данных для компьютера с четырьмя процессорами.
В этом примере тип, использующий Alloc функции и Free , обеспечивает лучшую производительность памяти, поскольку Alloc функции и Free оптимизированы для частого выделения и освобождения блоков памяти из нескольких потоков.
Компиляция кода
скопируйте пример кода и вставьте его в Visual Studio проект или вставьте в файл с именем allocators.cpp , а затем выполните следующую команду в Visual Studio окне командной строки.
cl.exe/EHsc распределителя. cpp
Как работает динамическая память и какими операторами пользоваться для работы с ней в C++?
malloc
В предыдущей главе уже обсуждалось, что локальные переменные кладутся на стек и существую до тех пор, пока мы не вышли из функции. С одной стороны, это позволяет автоматически очищать память, с другой стороны, существует необходимость в переменных, время жизни которых мы можем контролировать самостоятельно. Кроме того, нам необходимо динамическое выделение памяти, когда размер используемого пространства заранее не известен. Для этого используется выделение памяти на куче. Недостатков у такого подхода два: во-первых, память необходимо вручную очищать, во-вторых, выдеение памяти – достаточно дорогостоящая операция.
Для выделения памяти на куче в си используется функция malloc (memory allocation) из библиотеки stdlib.h
Функция выделяет size байтов памяти и возвращает указатель на неё. Если память выделить не удалось, то функция возвращает NULL. Так как malloc возвращает указатель типа void, то его необходимо явно приводить к нужному нам типу. Например, создадим указатель, после этого выделим память размером в 100 байт.
После того, как мы поработали с памятью, необходимо освободить память функцией free.
Используя указатель, можно работать с выделенной памятью как с массивом. Пример: пользователь вводит число – размер массива, создаём массив этого размера и заполняем его квадратами чисел по порядку. После этого выводим и удаляем массив.
Здесь (int *) – приведение типов. Пишем такой же тип, как и у указателя.
size * sizeof(int) – сколько байт выделить. sizeof(int) – размер одного элемента массива.
После этого работаем с указателем точно также, как и с массивом. В конце не забываем удалять выделенную память.
Теперь представим на рисунке, что у нас происходило. Пусть мы ввели число 5.
Функция malloc выделила память на куче по определённому адресу, после чего вернула его. Теперь указатель p хранит этот адрес и может им пользоваться для работы. В принципе, он может пользоваться и любым другим адресом.
Когда функция malloc "выделяет память", то она резервирует место на куче и возвращает адрес этого участка. У нас будет гарантия, что компьютер не отдаст нашу память кому-то ещё. Когда мы вызываем функцию free, то мы освобождаем память, то есть говорим компьютеру, что эта память может быть использована кем-то другим. Он может использовать нашу память, а может и нет, но теперь у нас уже нет гарантии, что эта память наша. При этом сама переменная не зануляется, она продолжает хранить адрес, которым ранее пользовалась.
Это очень похоже на съём номера в отеле. Мы получаем дубликат ключа от номера, живём в нём, а потом сдаём комнату обратно. Но дубликат ключа у нас остаётся. Всегда можно зайти в этот номер, но в нём уже кто-то может жить. Так что наша обязанность – удалить дубликат.
Иногда думают, что происходит "создание" или "удаление" памяти. На самом деле происходит только перераспределение ресурсов.
Различные аргументы realloc и malloc.
При вызове функции malloc, realloc и calloc с нулевым размером поведение не определено. Это значит, что может быть возвращён как NULL, так и реальный адрес. Им можно пользоваться, но к нему нельзя применять операцию разадресации.
Вызов realloc(NULL, size_t) эквиваленте вызову malloc(size_t).
Однако, вызов realloc(NULL, 0) не эквивалентен вызову malloc(0) :) Понимайте это, как хотите.
Ошибки при выделении памяти
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. В этом случае поведение не определено.
Два указателя на одну область памяти это вообще-то не ошибка. Бывают ситуации, когда без них не обойтись. Но это очередное минное поле для программиста.
Пример: функции swap и reverse_array
В следующем примере показаны функции swap и reverse_array . swap Функция обменивается содержимым массива по указанным индексам. Она выделяет память из кучи для временной переменной. reverse_array Функция создает большой массив и подсчитывает время, необходимое для того, чтобы несколько раз обращаться к этому массиву в параллельном режиме.
2 ответа 2
Вы же работаете с С++ - используйте его идиоматику.
Каждый отвечает за себя :)
Если вы позарез хотите писать на С++, как на С - ну, что-то вроде
Ну зачем вы так :) В женщине должна быть тайна :) Вот она и не хочет показывать свой код, чтобы остаться загадкой :)
@Mikhailo Да надоело - ваш код не работает! - а потом выясняется, что человек сам глупость написал. Словом, манеру выставлять вопросы/претензии без кода я считаю просто некорректным.
Выделение памяти под массив может оказаться не удачным, поскольку квадрат числа может оказаться слишком большим числом(да и само переданное число), и в памяти может не быть столько свободного места для такого массива. В этом случаи будет выброшено исключение, указатель останется нулевым, а освобождение нулевой памяти, невозможным. Поэтому нужно это проверить и уменьшить размер до тех пор, пока память не будет выделена. На С++ код может выглядеть следующим образом:
"а освобождение нулевой памяти невозможным" - точно? delete и free умеют корректно с nullptr работать
@KoVadim , а я не говорил, что освобождение нулевой памяти является ошибкой, просто невозможно удалять то, чего нет. И невозможность еще будет связана с тем, что будет выброшено исключение, и объект не создастся.
А какой смысл в выделении меньшего количества памяти, чем нужно? Логично просто сгенерировать исключение (оно само сгенерируется), а не пытаться выделить меньше памяти, чем нужно.
Размещающий new
Ничего не выделяет, возвращают аргумент ptr .
Позволяют вызвать конструктор объекта.
Размещающий operator delete также существует, и также ничего не делает. Нужен только потому что new-выражение требудет наличия operator delete .
Выражения new и delete для одного объекта
Функции operator new и operator delete только выделяют и освобождают память. Конструирование и разрушение объекта происходит в самом выражении new и delete . Для вызова конструктора используется new который не выделяет память - размещающий new .
Из кода выше видно, что new-выражение требует обе функции, operator new и operator delete .
2. Простой пул памяти.
У же оговаривалось ранее, что частое выделение памяти под множество мелких объектов, а также изменение размера выделенной области памяти замедляет работу и приводит к фрагментации памяти. Создадим просто пул памяти – заранее зарезервированную область памяти, которую будем раздавать. Для простоты сделаем пул глобальной переменной.
Здесь mempool – это наш пул, двумерный массив. msize – число строк, соответствует числу блоков памяти, размер которых задаётся пользователем при создании, checkbox – массива флагов. Длина массива флагов msize. Если i-й флаг поднят, то i-й блок памяти выделен.
- 1) Создаём пул
- 2) При запросе памяти проходим по массиву checkbox и находим первый ненулевой сегмент. Возвращаем этот сегмент и помечаем в checkbox, что сегмент был выделен.
- 3) При очистке памяти проходим по массиву mempool и сравниваем указатели. Если они равны, то в соответствующую ячейку checkbox заносим ноль.
Всё ещё не понятно? – пиши вопросы на ящик
Теги: Си память, malloc, calloc, realloc, free, Ошибки выделения памяти, Висячие указатели, Динамические массивы, Многомерные динамические массивы.
Функции operator new и operator delete в стандартной библиотеке
Динамические массивы, new[] и delete[]
Функции operator new[] и operator delete[] служат для выделения памяти, и ничем не отличаются от функций для одного объекта. Стандартные реализации просто вызывают operator new :
Размер динамического массива сохраняется самим выражением new[] , примерно так:
Массивы созданные при помощи выражения new T[N] могут быть освобождены только через delete[] ,
и наоборот, объекты созданные new T() , должны быть быть освобождены delete .
realloc
Е щё одна важная функция – realloc (re-allocation). Она позволяет изменить размер ранее выделенной памяти и получает в качестве аргументов старый указатель и новый размер памяти в байтах:
Функция realloc может как использовать ранее выделенный участок памяти, так и новый. При этом не важно, меньше или больше новый размер – менеджер памяти сам решает, где выделять память.
Пример – пользователь вводит слова. Для начала выделяем под слова массив размером 10. Если пользователь ввёл больше слов, то изменяем его размер, чтобы хватило места. Когда пользователь вводит слово end, прекращаем ввод и выводим на печать все слова.
Хочу обратить внимание, что мы при выделении памяти пишем sizeof(char*), потому что размер указателя на char не равен одному байту, как размер переменной типа char.
Размещающий new[]
Он существует, но мы не знаем сколько байт будут резервироваться копилятором под размер объекта, поэтому не сдедует его использовать:
Динамическая память
Выделяется и освобождается — с помощью специальных инструкций (т. е. по инициативе разработчика). Это позволяет по ходу работы программы контролировать и корректировать объём используемой памяти и, следовательно, создавать программы способные обрабатывать большие объёмы данных, обходя ограниченность физической памяти машины. Выделяется во время работы программы. Все объекты, выделяемые динамически, размещаются в куче (heap). Если не освобождать динамическую память, то у вас memory leak. Динамическая память, как и любая память, будет освобождена по завершении программы (учтите это, ибо выше писалось только про освобождение разработчиком). Работа с динамической памятью
Выделяется память с помощью оператора new, а освобождается — с помощью оператора delete. В момент, когда динамическая память выделена, она должна быть связана с некоторым указателем, подходящего типа (при выделении указывается тип и количество необходимых ячеек данного типа).
Если не освобождать динамическую память, то она будет занята до завершения программы, что неприемлемо. При выделении одной динамической переменной (одной ячейки памяти), можно сразу инициализировать её значение:
Можно выделять сразу несколько ячеек динамической памяти, получая динамический массив. Для этого его размер указывается в квадратных скобках после типа. Чтобы удалить динамический массив и освободить память используется оператор delete[].
Cразу после создания динамический массив автоматически заполняется нулями (в отличии от обычного массива в статической или стековой памяти). Если в указатель, уже хранящий адрес какого-то фрагмента динамической памяти, записать новый адрес, то фрагмент динамической памяти будет потерян, т. е. он не будет освобождён, но к нему никак нельзя будет обратиться (например, чтобы освободить этот фрагмент).
Проблема становится особенно острой, когда в памяти теряются целые массивы (они занимают больше места, чем отдельные переменные).
Си: Стандартные функции динамического выделения памяти Функции динамического выделения памяти находят в оперативной памяти непрерывный участок требуемой длины и возвращают начальный адрес этого участка. Функции динамического распределения памяти:
Для использования функций динамического распределения памяти необходимо подключение библиотеки malloc.h:
Поскольку все представленные функции в качестве возвращаемого значения имеют указатель на пустой тип void, требуется явное приведение типа возвращаемого значения. Для определения размера массива в байтах, используемого в качестве аргумента функции malloc()требуется количество элементов умножить на размер одного элемента. Поскольку элементами массива могут быть как данные простых типов, так и составных типов (например, структуры), для точного определения размера элемента в общем случае рекомендуется использование функции int sizeof(тип); которая определяет количество байт, занимаемое элементом указанного типа. Память, динамически выделенная с использованием функций calloc(), malloc(), может быть освобождена с использованием функции free(указатель); "Правилом хорошего тона" в программировании является освобождение динамически выделенной памяти в случае отсутствия ее дальнейшего использования. Однако если динамически выделенная память не освобождается явным образом, она будет освобождена по завершении выполнения программы. Функция realloc выполняет перераспределение блоков памяти. Размер блока памяти, на который ссылается параметр ptrmem изменяется на size байтов. Блок памяти может уменьшаться или увеличиваться в размере. Эта функция может перемещать блок памяти на новое место, в этом случае функция возвращает указатель на новое место в памяти. Содержание блока памяти сохраняется даже если новый блок имеет меньший размер, чем старый. Отбрасываются только те данные, которые не вместились в новый блок. Если новое значение size больше старого, то содержимое вновь выделенной памяти будет неопределенным. В случае, если ptrmem равен NULL, функция ведет себя именно так, как функция malloc, т. е. выделяет память и возвращает указатель на этот участок памяти. В случае, если size равен 0, ранее выделенная память будет освобождена, как если бы была вызвана функция free, и возвращается нулевой указатель.
Примеры: Выделение памяти при помощи malloc:
Выделение при помощи calloc:
Выделение памяти при помощи realloc:
Небольшие заметки по поводу памяти в плюсах (на основе опыта, исправляйте ошибки и дополняйте меня): если у нас недостаточно памяти, valloc, calloc и realloc возвращают NULL. Поэтому проверки на null поинетры нужны. В плюсах new либо вернёт значение, либо кинет исключение; если вы пытаетесь использовать память, которая вам не принадлежит, то это undefined behavior. Может быть, вас убъёт ось за то, что лезете не в свой кусок памяти. Может, упадёте с segmentation fault, или попортите данные своего же приложения и будете долго и муторно искать ошибку не там, а может быть, у вас девушка забеременеет. Или всё будет работать. То, что malloc выглядит функцией для инициализации одной переменной, а calloc – для массива – это херня. Все три (включая realloc) выделяют последовательный кусок памяти, так что хоть malloc(n * sizeof(int)), хоть calloc(n, sizeof(int)) – одна херня. в память только для чтения нельзя писать, например:
NULL поинтеру нельзя присвавать какое либо значение, это segmentation fault. Например:
Примеры
1. Простое скользящее среднее равно среднему арифметическому функции за период n. Пусть у нас имеется ряд измерений значения функции. Часто эти измерения из-за погрешности "плавают" или на них присутствуют высокочастотные колебания. Мы хотим сгладить ряд, для того, чтобы избавиться от этих помех, или для того, чтобы выявить общий тренд. Самый простой способ: взять n элементов ряда и получить их среднее арифметическое. n в данном случае - это период простого скользящего среднего. Так как мы берём n элементов для нахождения среднего, то в результирующем массиве будет на n чисел меньше.
Это простой пример. Большая его часть связана со считыванием данных, вычисление среднего всего в девяти строчках.
2. Сортировка двумерного массива. Самый простой способ сортировки - перевести двумерный массив MxN в одномерный размером M*N, после чего отсортировать одномерный массив, а затем заполнить двумерный массив отсортированными данными. Чтобы не тратить место под новый массив, мы поступим по-другому: если проходить по всем элементам массива k от 0 до M*N, то индексы текущего элемента можно найти следующим образом:
j = k / N;
i = k - j*M;
Заполним массив случайными числами и отсортируем
3. Бином Ньютона. Создадим треугольную матрицу и заполним биномиальными коэффициентами
Если Вы желаете изучать этот материал с преподавателем, советую обратиться к репетитору по информатике
Всё ещё не понятно? – пиши вопросы на ящик
В этом документе показано, как использовать функции Concurrency:: Alloc и Concurrency:: Free для повышения производительности памяти. Он сравнивает время, необходимое для изменения числа элементов массива в параллельном режиме для трех различных типов, каждый из new которых указывает операторы и delete .
Alloc Функции и Free наиболее полезны, когда несколько потоков часто вызывают Alloc и Free . Среда выполнения содержит отдельный кэш памяти для каждого потока; Таким образом, среда выполнения управляет памятью без использования блокировок или барьеров памяти.
Пример: Функция wmain
В следующем примере показана wmain функция, вычисляющая время, необходимое для reverse_array работы функции new_delete в типах, malloc_free и Alloc_Free , каждая из которых использует другую схему выделения памяти.
Обычный new
Пользователь может установить свою функцию new_handler при помощи функции std::set_new_handler .
Не-бросающий исключений new
1. Выделение памяти с явным сохранением размера области.
К огда мы работаем с массивом, то необходимо хранить его размер. Для этого нужно постоянно носить с собой переменную, которую нужно передавать в функции или возвращать из функции. Способ реализации функции malloc не определён в документации языка, известно только, что в большинстве случаев выделенный участок памяти начинается с метаданных, которые хранят размер области. В различных компиляторах реализованы свои функции, которые могут разбирать метаданные и возвращать размер области памяти.
Реализуем функцию malloc, которая будет явно сохранять размер участка памяти и не будет зависеть от реализации компилятора. Для этого, в начале массива будет сохранять его размер в структуре _METADATA_.
Структура содержит всего одно поле – размер size типа size_t. В будущем, если понадобится, без труда можно будет добавить новые поля. Теперь – функция my_malloc. Она получает размер участка памяти и возвращает указатель на выделенный участок.
1. Выделяем память. Её должно быть на sizeof(_METADATA_) байт больше, чтобы разместить заголовок.
2. Если произошла ошибка и malloc вернула нулевой указатель, то выходим из функции
3. Теперь выделенную область кастуем до структуры _METADATA_ и записываем туда размер
4. Возвращаем указатель со сдвигом на sizeof(_METADATA_), таким образом, он будет иметь размер size
(char*) нужно для того, чтобы прибавить к указателю неопределённого типа число байт, равное размеру _METADATA_.
Функция my_free должна освобождать участок памяти, выделенный функцией my_malloc. Для этого необходимо сдвинуть указатель на размер структуры с метаданными.
Функция get_size принимает указатель, полученный от функции my_malloc и читает размер массива из заголовка. Для этого сдвигаемся к началу области, кастуем её до _METADATA_ и обращаемся к полю size
Функция realloc похожа на malloc. Выделяем память под новый массив размера большего на sizeof(_METADATA_). Заносим в заголовок новый размер.
Проблемы возникают с функцией calloc. Так как она принимает размер массива и размер его элементов, то размер нашего заголовка может быть не кратен размеру элемента. Будет выделять память функцией malloc, а затем функцией memset заполнять выделенную область нулями.
Теперь прикроем стандартные функции макросами
Соберём всё вместе с примерами
Читайте также: