У Java есть автоматический сборщик мусора, который время от времени останавливает мир, но заботится о мусоре в куче. Теперь приложения на C / C ++ не имеют таких зависаний STW, их использование памяти также не увеличивается бесконечно. Как достигается это поведение? Как ухаживать за мертвыми предметами?
c++
c
garbage-collection
Джу Шуа
источник
источник
new
.What happens to garbage in C++?
Разве это обычно не компилируется в исполняемый файл?Ответы:
Программист несет ответственность за
new
удаление объектов, созданных с помощьюdelete
. Если объект создан, но не уничтожен до того, как последний указатель или ссылка на него выйдут из области видимости, он провалится через трещины и станет утечкой памяти .К сожалению, для C, C ++ и других языков, которые не включают GC, это просто накапливается со временем. Это может привести к тому, что приложению или системе не хватит памяти, и они не смогут выделить новые блоки памяти. На этом этапе пользователь должен прибегнуть к прекращению работы приложения, чтобы операционная система могла вернуть использованную память.
Что касается смягчения этой проблемы, есть несколько вещей, которые значительно облегчают жизнь программиста. Это в первую очередь поддерживается характером области .
Здесь мы создали две переменные. Они существуют в Block Scope , как определено
{}
фигурными скобками. Когда выполнение выходит из этой области, эти объекты будут автоматически удалены. В этом случае,variableThatIsAPointer
как следует из его названия, указатель на объект в памяти. Когда он выходит из области видимости, указатель удаляется, но объект, на который он указывает, остается. Здесь мыdelete
рассматриваем этот объект до того, как он выходит из области видимости, чтобы гарантировать отсутствие утечки памяти. Однако мы могли бы также передать этот указатель в другом месте и ожидать, что он будет удален позже.Эта природа области действия распространяется на классы:
Здесь применяется тот же принцип. Нам не нужно беспокоиться о том,
bar
когдаFoo
удаляется. Однако дляotherBar
, только указатель удаляется. ЕслиotherBar
это единственный действительный указатель на какой-либо объект, на который он указывает, мы, вероятно, должны использоватьdelete
его вFoo
деструкторе. Это движущая концепция RAIIRAII также является типичной движущей силой Smart Pointers . В стандартной библиотеке C ++, это
std::shared_ptr
,std::unique_ptr
иstd::weak_ptr
; хотя я видел и использовал другиеshared_ptr
/weak_ptr
реализации, которые следуют тем же понятиям. Для них счетчик ссылок отслеживает, сколько указателей существует на данный объект, и автоматически получает объект,delete
когда на него больше нет ссылок.Помимо этого, все сводится к надлежащей практике и дисциплине для программиста, чтобы гарантировать, что их код обрабатывает объекты должным образом.
источник
delete
- вот что я искал. Потрясающие.delete
Автоматически вызывается для вас смарт - указатели , если вы используете их , так что вы должны рассмотреть возможность использования их каждый раз , когда автоматическое хранение не может быть использован.delete
код приложения (и начиная с C ++ 14 и далее, то же самое сnew
), а вместо этого использовать умные указатели и RAII для удаления объектов кучи.std::unique_ptr
Тип иstd::make_unique
функция являются прямой, самой простой заменойnew
иdelete
на уровне кода приложения.C ++ не имеет сборки мусора.
Приложения C ++ должны утилизировать свой мусор.
Разработчики приложений C ++ должны понимать это.
Когда они забывают, результат называется «утечка памяти».
источник
new
иdelete
.malloc
иfree
, илиnew[]
иdelete[]
, или любым другим распределителям (как для Windows - хGlobalAlloc
,LocalAlloc
,SHAlloc
,CoTaskMemAlloc
,VirtualAlloc
,HeapAlloc
, ...), и память , выделенная для вас (например , с помощьюfopen
).В C, C ++ и других системах без сборщика мусора, разработчик предлагает язык и его библиотеки, чтобы указать, когда можно восстановить память.
Основным средством является автоматическое хранение . Во многих случаях сам язык гарантирует, что предметы утилизируются:
В этом случае компилятор должен знать, когда эти значения не используются, и восстанавливать хранилище, связанное с ними.
При использовании динамического хранения в C память традиционно выделяется
malloc
и восстанавливается с помощьюfree
. В C ++ память традиционно выделяетсяnew
и восстанавливается с помощьюdelete
.C не сильно изменился за эти годы, однако современный C ++ избегает
new
иdelete
полностью и полагается вместо этого на библиотечные средства (которые сами используютnew
иdelete
соответственно):std::unique_ptr
иstd::shared_ptr
std::string
,std::vector
,std::map
, ... все внутри управления динамически выделенной памяти прозрачноГоворя о том
shared_ptr
, что существует риск: если цикл ссылок сформирован, а не нарушен, то утечка памяти может быть. Разработчик должен избежать этой ситуации, самый простой способ -shared_ptr
полностью избежать , а второй - избежать циклов на уровне типа.В результате утечки памяти не являются проблемой в C ++ даже для новых пользователей, если они не используют их
new
,delete
илиstd::shared_ptr
. Это не похоже на C, где необходима стойкая дисциплина и, как правило, недостаточно.Тем не менее, этот ответ не будет полным без упоминания сестры-близнеца утечки памяти: висячие указатели .
Висячий указатель (или свисающая ссылка) - это опасность, создаваемая сохранением указателя или ссылки на мертвый объект. Например:
Использование висячего указателя или ссылки является неопределенным поведением . В общем, к счастью, это немедленный сбой; довольно часто, к сожалению, это сначала вызывает повреждение памяти ... и время от времени возникает странное поведение, потому что компилятор выдает действительно странный код.
Неопределенное поведение - самая большая проблема с C и C ++ на сегодняшний день, с точки зрения безопасности / правильности программ. Возможно, вы захотите проверить Rust для языка без сборщика мусора и без неопределенного поведения.
источник
new
,delete
иshared_ptr
"; безnew
и уshared_ptr
вас есть прямое владение, поэтому нет утечек. Конечно, у вас могут быть висячие указатели и т. Д., Но я боюсь, что вам нужно покинуть C ++, чтобы избавиться от них.В C ++ есть такая вещь, как RAII . В основном это означает, что мусор убирается, когда вы идете, а не оставляете его в куче и позволяете уборщику убирать за вами. (представьте, что я в своей комнате смотрю футбол - пока я пью банки с пивом и мне нужны новые, путь C ++ - это подносить пустую банку в мусорное ведро по пути к холодильнику, а C # - бросать ее на пол и ждать, пока горничная поднимет их, когда придет чистить).
Теперь в C ++ возможна утечка памяти, но для этого необходимо оставить обычные конструкции и вернуться к способу выполнения действий на C - выделению блока памяти и отслеживанию того, где этот блок находится без какой-либо помощи языка. Некоторые люди забывают этот указатель и поэтому не могут удалить блок.
источник
Следует отметить, что в случае с C ++ распространенным заблуждением является то, что «вам необходимо выполнить ручное управление памятью». Фактически, вы обычно не управляете памятью в своем коде.
Объекты фиксированного размера (с продолжительностью жизни)
В подавляющем большинстве случаев, когда вам нужен объект, он будет иметь определенное время жизни в вашей программе и будет создан в стеке. Это работает для всех встроенных примитивных типов данных, но также для экземпляров классов и структур:
Стековые объекты автоматически удаляются при завершении функции. В Java объекты всегда создаются в куче, и поэтому должны быть удалены каким-либо механизмом, например сборкой мусора. Это не проблема для объектов стека.
Объекты, которые управляют динамическими данными (с временем жизни области)
Использование пространства в стеке работает для объектов фиксированного размера. Когда вам нужен переменный объем пространства, например, массив, используется другой подход: список инкапсулируется в объект фиксированного размера, который управляет динамической памятью для вас. Это работает, потому что объекты могут иметь специальную функцию очистки, деструктор. Он гарантированно вызывается, когда объект выходит из области видимости и делает противоположное конструктору:
В коде, где используется память, управление памятью вообще отсутствует. Единственное, что нам нужно, это убедиться, что у написанного нами объекта есть подходящий деструктор. Независимо от того, как мы покидаем область действия
listTest
, будь то через исключение или просто возвращаясь из него, вызывается деструктор~MyList()
, и нам не нужно управлять какой-либо памятью.(Я думаю, что это забавное дизайнерское решение использовать двоичный оператор NOT
~
для обозначения деструктора. При использовании над числами он инвертирует биты; по аналогии, здесь он указывает, что то, что сделал конструктор, инвертировано.)В основном все объекты C ++, которым требуется динамическая память, используют эту инкапсуляцию. Он был назван RAII («получение ресурсов - инициализация»), что является довольно странным способом выразить простую идею о том, что объекты заботятся о своем собственном содержимом; что они приобретают, так это их убирать.
Полиморфные объекты и время жизни за пределами видимости
Теперь оба этих случая касались памяти с четко определенным временем жизни: время жизни совпадает с областью действия. Если мы не хотим, чтобы срок действия объекта истек при выходе из области действия, существует третий механизм, который может управлять памятью для нас: умный указатель. Умные указатели также используются, когда у вас есть экземпляры объектов, тип которых меняется во время выполнения, но которые имеют общий интерфейс или базовый класс:
Существует еще один вид интеллектуального указателя
std::shared_ptr
, предназначенный для совместного использования объектов несколькими клиентами. Они удаляют свой содержащийся объект только тогда, когда последний клиент выходит из области видимости, поэтому их можно использовать в ситуациях, когда совершенно неизвестно, сколько будет клиентов и как долго они будут использовать этот объект.Таким образом, мы видим, что вы на самом деле не делаете никакого ручного управления памятью. Все заключено в капсулу, а затем об этом заботятся с помощью полностью автоматического управления памятью на основе области действия. В случаях, когда этого недостаточно, используются интеллектуальные указатели, которые инкапсулируют необработанную память.
Считается крайне плохой практикой использовать необработанные указатели в качестве владельцев ресурсов где-либо в коде C ++, необработанные выделения вне конструкторов и необработанные
delete
вызовы вне деструкторов, поскольку ими практически невозможно управлять при возникновении исключений и, как правило, трудно безопасно использовать.Лучшее: это работает для всех типов ресурсов
Одним из самых больших преимуществ RAII является то, что он не ограничивается памятью. На самом деле он предоставляет очень естественный способ управления ресурсами, такими как файлы и сокеты (открытие / закрытие) и механизмами синхронизации, такими как мьютексы (блокировка / разблокировка). По сути, каждый ресурс, который может быть получен и должен быть освобожден, управляется точно так же в C ++, и ни одно из этого управления не остается на усмотрение пользователя. Все это заключено в классы, которые получают в конструкторе и освобождают в деструкторе.
Например, функция, блокирующая мьютекс, обычно пишется так в C ++:
Другие языки делают это намного более сложным, требуя, чтобы вы делали это вручную (например, в
finally
предложении), или они порождают специализированные механизмы, которые решают эту проблему, но не особенно изящно (обычно позже в их жизни, когда достаточно людей пострадал от недостатка). Такие механизмы - это try-with-resources в Java и оператор using в C #, оба они являются приближением RAII C ++.Подводя итог, можно сказать, что все это было очень поверхностным описанием RAII в C ++, но я надеюсь, что это поможет читателям понять, что управление памятью и даже ресурсами в C ++ обычно не «ручное», а фактически автоматическое.
источник
delete
и ты мертв», ответы набирают более 30 баллов и получают признание, а у этого пять. Кто-нибудь на самом деле использует C ++ здесь?Что касается C, то этот язык не дает вам инструментов для управления динамически выделяемой памятью. Вы несете полную ответственность за то, чтобы у каждого
*alloc
было что-free
то соответствующее .Когда вещи становятся действительно неприятными, это когда распределение ресурсов не удается на полпути; Вы пытаетесь снова, вы откатываетесь и начинаете сначала, вы откатываетесь и выходите с ошибкой, вы просто освобождаетесь от обязательств и позволяете ОС справиться с этим?
Например, вот функция для выделения несмежного 2D-массива. Поведение здесь таково, что если сбой выделения происходит в середине процесса, мы откатываем все назад и возвращаем индикацию ошибки, используя указатель NULL:
Этот код неприятен с этими
goto
s, но, при отсутствии какого-либо механизма структурированной обработки исключений, это практически единственный способ справиться с проблемой, не выполняя полную выручку, особенно если ваш код распределения ресурсов вложен больше чем одна петля глубиной. Это один из немногих случаев, когдаgoto
на самом деле привлекательный вариант; в противном случае вы используете кучу флагов и дополнительныхif
операторов.Вы можете облегчить себе жизнь, написав выделенные функции распределителя / освобождения для каждого ресурса, что-то вроде
источник
goto
заявлениями. Это рекомендуемая практика в некоторых областях. Это часто используемая схема защиты от эквивалента исключений в C. Посмотрите на код ядра Linux, который полонgoto
операторов - и который не пропускает.goto
посторонний. Было бы более читаемым , если вы изменилиgoto done;
вreturn arr;
иarr=NULL;done:return arr;
кreturn NULL;
. Хотя в более сложных случаях действительно может быть несколькоgoto
s, начинающих развертываться с разными уровнями готовности (что будет сделано при размотке стека исключений в C ++).Я научился классифицировать проблемы с памятью на несколько разных категорий.
Один раз капает. Предположим, что программа пропускает 100 байт во время запуска, но никогда больше не будет. Поиск и устранение этих разовых утечек - это хорошо (мне нравится иметь чистый отчет с помощью функции обнаружения утечек), но это не обязательно. Иногда возникают более серьезные проблемы, которые нужно атаковать.
Повторные утечки. Функция, которая вызывается многократно в течение жизненного цикла программы и регулярно теряет память, является большой проблемой. Эти потеки замучат программу и, возможно, ОС до смерти.
Взаимные ссылки. Если объекты A и B ссылаются друг на друга через общие указатели, вы должны сделать что-то особенное, либо в дизайне этих классов, либо в коде, который реализует / использует эти классы, чтобы разорвать цикличность. (Это не проблема для языков с мусором.)
Вспоминая слишком много. Это злая двоюродная сестра мусора / утечки памяти. RAII здесь не поможет и не будет собирать мусор. Это проблема на любом языке. Если какая-то активная переменная имеет путь, который соединяет ее с каким-то случайным фрагментом памяти, этот случайный фрагмент памяти не является мусором. Сделать программу забывчивой, чтобы она могла работать в течение нескольких дней, сложно. Создание программы, которая может работать несколько месяцев (например, до тех пор, пока диск не выйдет из строя) очень и очень сложно.
У меня не было серьезных проблем с утечками в течение долгого времени. Использование RAII в C ++ очень помогает в устранении этих проблем. (Однако следует соблюдать осторожность с общими указателями.) Гораздо важнее, что у меня были проблемы с приложениями, использование памяти которых продолжает расти и расти, и растет из-за неразрывных соединений с памятью, которые больше не нужны.
источник
Программист C ++ должен реализовать свою собственную форму сборки мусора, где это необходимо. Невыполнение этого требования приведет к так называемой «утечке памяти». В языках «высокого уровня» (таких как Java) довольно часто встроена сборка мусора, в отличие от языков «низкого уровня», таких как C и C ++.
источник