Существует два широко используемых метода выделения памяти: автоматическое распределение и динамическое распределение. Обычно для каждой области памяти имеется соответствующая область: стек и куча.
стек
Стек всегда распределяет память последовательно. Это может быть сделано, потому что это требует от вас освобождения памяти в обратном порядке (первый вход, последний выход: FILO). Это техника выделения памяти для локальных переменных во многих языках программирования. Это очень, очень быстро, потому что требует минимальной бухгалтерии, а следующий адрес для выделения неявный.
В C ++ это называется автоматическим хранением, потому что хранилище запрашивается автоматически в конце области. Как только выполнение текущего блока кода (с использованием разделителя {}
) завершено, память для всех переменных в этом блоке автоматически собирается. Это также момент, когда деструкторы вызываются для очистки ресурсов.
отвал
Куча обеспечивает более гибкий режим выделения памяти. Бухгалтерия сложнее, а распределение медленнее. Поскольку нет неявной точки освобождения, вы должны освободить память вручную, используя delete
или delete[]
( free
в C). Тем не менее, отсутствие неявной точки освобождения является ключом к гибкости кучи.
Причины использовать динамическое распределение
Даже если использование кучи медленнее и потенциально приводит к утечкам памяти или фрагментации памяти, для динамического выделения есть совершенно хорошие сценарии использования, поскольку они менее ограничены.
Две основные причины использовать динамическое распределение:
Вы не знаете, сколько памяти вам нужно во время компиляции. Например, когда вы читаете текстовый файл в строку, вы обычно не знаете, какой размер у файла, поэтому вы не можете решить, какой объем памяти выделить, пока не запустите программу.
Вы хотите выделить память, которая будет сохраняться после выхода из текущего блока. Например, вы можете написать функцию, string readfile(string path)
которая возвращает содержимое файла. В этом случае, даже если в стеке может содержаться все содержимое файла, вы не сможете вернуться из функции и сохранить выделенный блок памяти.
Почему динамическое распределение часто не нужно
В C ++ есть аккуратная конструкция, называемая деструктором . Этот механизм позволяет вам управлять ресурсами путем выравнивания времени жизни ресурса с временем жизни переменной. Эта техника называется RAII и является отличительной чертой C ++. Он «оборачивает» ресурсы в объекты. std::string
это идеальный пример. Этот фрагмент:
int main ( int argc, char* argv[] )
{
std::string program(argv[0]);
}
на самом деле выделяет переменное количество памяти. std::string
Объект выделяет память , используя кучу и освобождает его в деструкторе. В этом случае вам не нужно было вручную управлять какими-либо ресурсами, и вы все равно получили преимущества динамического выделения памяти.
В частности, это означает, что в этом фрагменте:
int main ( int argc, char* argv[] )
{
std::string * program = new std::string(argv[0]); // Bad!
delete program;
}
отсутствует ненужное динамическое распределение памяти. Программа требует большего набора (!) И вводит риск забыть освободить память. Это делает это без видимой выгоды.
Почему вы должны использовать автоматическое хранение как можно чаще
По сути, последний абзац подводит итог. Использование автоматического хранения как можно чаще делает ваши программы:
- быстрее набирать текст;
- быстрее при беге;
- менее подвержен утечкам памяти / ресурсов.
Бонусные очки
В указанном вопросе есть дополнительные проблемы. В частности, следующий класс:
class Line {
public:
Line();
~Line();
std::string* mString;
};
Line::Line() {
mString = new std::string("foo_bar");
}
Line::~Line() {
delete mString;
}
На самом деле использовать намного более рискованно, чем следующий:
class Line {
public:
Line();
std::string mString;
};
Line::Line() {
mString = "foo_bar";
// note: there is a cleaner way to write this.
}
Причина в том, что std::string
правильно определяет конструктор копирования. Рассмотрим следующую программу:
int main ()
{
Line l1;
Line l2 = l1;
}
Используя оригинальную версию, эта программа, скорее всего, потерпит крах, так как она использует delete
одну и ту же строку дважды. Используя измененную версию, каждый Line
экземпляр будет иметь свой собственный экземпляр строки , каждый со своей памятью, и оба будут выпущены в конце программы.
Другие заметки
Широкое использование RAII считается лучшей практикой в C ++ по всем вышеуказанным причинам. Однако есть дополнительное преимущество, которое не сразу очевидно. В принципе, это лучше, чем сумма его частей. Весь механизм составляет . Это масштабируется.
Если вы используете Line
класс в качестве строительного блока:
class Table
{
Line borders[4];
};
затем
int main ()
{
Table table;
}
выделяет четыре std::string
экземпляра, четыре Line
экземпляра, один Table
экземпляр и все содержимое строки, и все освобождается автоматически .
Monster
выплевываетTreasure
к тому,World
когда он умирает. В своемDie()
методе он добавляет сокровище в мир. Его нужно использоватьworld->Add(new Treasure(/*...*/))
в другом, чтобы сохранить сокровище после его смерти. Альтернативыshared_ptr
(могут быть излишними),auto_ptr
(плохая семантика для передачи права собственности), передача по значению (расточительная) иmove
+unique_ptr
(пока не получили широкого применения).Потому что стек быстрее и герметичнее
В C ++ требуется всего одна инструкция для выделения пространства - в стеке - для каждого локального объекта области в данной функции, и утечка этой памяти невозможна. Этот комментарий намеревался (или должен был) сказать что-то вроде «используйте стек, а не кучу».
источник
int x; return &x;
Причина, почему это сложно.
Во-первых, C ++ не является сборщиком мусора. Поэтому для каждого нового должно быть соответствующее удаление. Если вы не можете вставить это удаление, значит, у вас утечка памяти. Теперь для простого случая, подобного этому:
Это просто Но что произойдет, если «Do stuff» создаст исключение? Упс: утечка памяти. Что произойдет, если «Делать вещи» выдает
return
рано? Упс: утечка памяти.И это для простейшего случая . Если вам случится вернуть эту строку кому-то, теперь он должен удалить ее. И если они передадут это в качестве аргумента, нужно ли его получателю удалить? Когда они должны удалить это?
Или вы можете просто сделать это:
Нет
delete
. Объект был создан в «стеке», и он будет уничтожен после выхода из области видимости. Вы даже можете вернуть объект, передав его содержимое вызывающей функции. Вы можете передать объект в функции (обычно в качестве ссылки или const-ссылки:void SomeFunc(std::string &iCanModifyThis, const std::string &iCantModifyThis)
и т. Д.Все без
new
иdelete
. Нет сомнений в том, кому принадлежит память или кто отвечает за ее удаление. Если вы делаете:Понятно , что
otherString
имеет копию данных оsomeString
. Это не указатель; это отдельный объект. Они могут иметь одинаковое содержимое, но вы можете изменить одно, не влияя на другое:Видишь идею?
источник
main()
, существует на время программы, не может быть легко создан в стеке из-за ситуации, и указатели на него передаются любым функциям, которые требуют к нему доступа Может ли это привести к утечке в случае сбоя программы, или это будет безопасно? Я бы предположил последнее, поскольку ОС, освобождающая всю память программы, должна также логически освобождать ее, но я не хочу ничего предполагать, когда дело доходит доnew
.Созданные объекты
new
должны в конечном итоге бытьdelete
утрачены. Деструктор не будет вызван, память не будет освобождена, в целом. Поскольку в C ++ нет сборки мусора, это проблема.Объекты, созданные по значению (т. Е. В стеке), автоматически умирают, когда выходят из области видимости. Вызов деструктора вставляется компилятором, а память автоматически освобождается при возврате функции.
Интеллектуальные указатели, например
unique_ptr
,shared_ptr
решают висячую проблему ссылок, но они требуют дисциплины кодирования и имеют другие потенциальные проблемы (копируемость, циклы ссылок и т. Д.).Кроме того, в многопоточных сценариях,
new
это является предметом спора между потоками; может быть влияние на производительность для чрезмерного использованияnew
. Создание объекта стека по определению является локальным для потока, поскольку каждый поток имеет свой собственный стек.Недостатком объектов-значений является то, что они умирают после возврата из функции хоста - вы не можете передать ссылку на них обратно вызывающей стороне, только копируя, возвращая или перемещая по значению.
источник
new
должны быть в конечном итоге,delete
чтобы они утекли". - что еще хуже,new[]
должно соответствоватьdelete[]
, и вы получите неопределенное поведение, если выdelete
new[]
-ed память илиdelete[]
new
-ed память - очень немногие компиляторы предупреждают об этом (некоторые инструменты, такие как Cppcheck, когда они могут).источник
malloc()
или его друзей для выделения необходимой памяти. Однако стек не может освободить какой-либо элемент в стеке, единственный способ, которым когда-либо освобождается память стека, - это раскручивание с вершины стека.Я вижу, что пропущено несколько важных причин сделать как можно меньше новых:
Оператор
new
имеет недетерминированное время выполненияВызов
new
может или не может заставить ОС выделять новую физическую страницу для вашего процесса, что может быть довольно медленным, если вы делаете это часто. Или у него уже может быть подходящая ячейка памяти, мы не знаем. Если ваша программа должна иметь согласованное и предсказуемое время выполнения (как в системе реального времени или симуляции игры / физики), вам нужно избегатьnew
циклов, критичных ко времени.Оператор
new
неявной синхронизации потоковДа, вы слышали меня, ваша ОС должна убедиться, что ваши таблицы страниц согласованы и, как
new
следствие этого, вызов вашего потока приобретет неявную блокировку мьютекса. Если вы постоянно вызываетеnew
из множества потоков, вы фактически сериализуете свои потоки (я сделал это с 32 процессорами, каждый из которыхnew
получил по несколько сотен байт каждый, ой! Это была королевская лава для отладки)Остальные, такие как медленный, фрагментация, склонность к ошибкам и т. Д., Уже упоминались в других ответах.
источник
void * someAddress = ...; delete (T*)someAddress
mlock()
или что-то подобное. Это связано с тем, что системе может не хватать памяти, и для стека нет готовых страниц физической памяти, поэтому ОС может потребоваться обменять или записать некоторые кэши (очистить грязную память) на диск, прежде чем можно будет продолжить выполнение.Pre-C ++ 17:
Потому что он подвержен тонким утечкам, даже если вы оберните результат в умный указатель .
Рассмотрим «осторожного» пользователя, который не забывает оборачивать объекты в умные указатели:
Этот код является опасным , потому что нет никакой гарантии , что либо
shared_ptr
строится , прежде чем либоT1
илиT2
. Следовательно, если один изnew T1()
илиnew T2()
потерпел неудачу после того, как другой преуспел, то первый объект будет пропущен, потому что неshared_ptr
существует, чтобы уничтожить и освободить его.Решение: использовать
make_shared
.Post-C ++ 17:
Это больше не проблема: C ++ 17 налагает ограничение на порядок этих операций, в этом случае гарантируя, что за каждым вызовомnew()
должен немедленно следовать построение соответствующего умного указателя, без каких-либо других операций между ними. Это подразумевает, что к тому времени, когдаnew()
вызывается второй , гарантируется, что первый объект уже обернут в его умный указатель, что предотвращает любые утечки в случае возникновения исключения.Более подробное объяснение нового порядка оценки, введенного C ++ 17, было предоставлено Барри в другом ответе .Спасибо @Remy Lebeau за то, что он указал, что это все еще проблема в C ++ 17 (хотя и в меньшей степени):
shared_ptr
конструктор может не выделить свой блок управления и выполнить команду throw, и в этом случае переданный ему указатель не удаляется.Решение: использовать
make_shared
.источник
new
онаshared_ptr
завершится успешно, а затем последующее построение завершится неудачно.std::make_shared()
решил бы это тожеshared_ptr
конструктор выделяет память для блока управления, в котором хранятся общий указатель и средство удаления, так что да, теоретически это может вызвать ошибку памяти. Только конструкторы копирования, перемещения и создания псевдонимов не выбрасывают.make_shared
выделяет общий объект внутри самого блока управления, поэтому вместо 2 выделяется только 1В значительной степени это тот, кто поднимает свои слабости до общего правила. Нет ничего плохого в создании объектов с помощью
new
оператора. Для этого есть какой-то аргумент, что вы должны делать это с некоторой дисциплиной: если вы создаете объект, вы должны быть уверены, что он будет уничтожен.Самый простой способ сделать это - создать объект в автоматическом хранилище, поэтому C ++ знает, как уничтожить его, когда он выходит из области видимости:
Теперь обратите внимание, что когда вы падаете, этот блок после конечной скобки
foo
выходит за рамки. C ++ автоматически вызовет свой dtor. В отличие от Java, вам не нужно ждать, пока GC найдет его.Ты написал
вы хотели бы явно сопоставить его с
или, что еще лучше, выделите их
File *
как «умный указатель». Если вы не будете осторожны с этим, это может привести к утечкам.Сам ответ делает ошибочное предположение, что если вы не используете,
new
вы не размещаете в куче; на самом деле, в C ++ вы этого не знаете. Самое большее, вы знаете, что небольшой объем памяти, скажем, один указатель, определенно выделяется в стеке. Тем не менее, подумайте, является ли реализация File чем-то вродетогда
FileImpl
будет равно будет выделяться в стеке.И да, вам лучше иметь
в классе тоже; без него, вы утечка памяти из кучи , даже если вы не по- видимому , выделить на кучу вообще.
источник
new
per se , но если вы посмотрите на исходный код, на который ссылался комментарий, тоnew
это злоупотребление. Код написан так, как если бы это был Java или C #, гдеnew
он используется практически для каждой переменной, когда в стеке есть куда больше смысла.new
. Это говорит о том, что если у вас есть выбор между динамическим размещением и автоматическим хранением, используйте автоматическое хранение.new
, но если вы используетеdelete
, вы делаете это неправильно!new()
не следует использовать в качестве Литтл , насколько это возможно. Его следует использовать как можно осторожнее . И это должно использоваться так часто, как это необходимо, как продиктовано прагматизмом.Распределение объектов в стеке, основанное на их неявном уничтожении, является простой моделью. Если требуемая область видимости объекта соответствует этой модели, тогда нет необходимости использовать ее
new()
с сопоставлениемdelete()
и проверкой указателей NULL. В случае, когда у вас много короткоживущих объектов размещения в стеке, следует уменьшить проблемы фрагментации кучи.Однако, если время жизни вашего объекта должно выходить за пределы текущей области, то
new()
это правильный ответ. Просто убедитесь, что вы обращаете внимание на то, когда и как вы звоните,delete()
и на возможности указателей NULL, используя удаленные объекты и все другие ошибки, которые приходят с использованием указателей.источник
const
ref или pointer ...?make_shared/_unique
это возможно) вызываемый абонент никогда не долженnew
илиdelete
. В этом ответе пропущены реальные моменты: (A) C ++ предоставляет такие вещи, как RVO, семантика перемещения и выходные параметры, что часто означает, что обработка создания объекта и продления срока службы путем возврата динамически распределенной памяти становится ненужной и небрежной. (B) Даже в ситуациях, когда требуется динамическое размещение, stdlib предоставляет оболочки RAII, которые освобождают пользователя от уродливых внутренних деталей.Когда вы используете новый, объекты размещаются в куче. Обычно используется, когда вы ожидаете расширения. Когда вы объявляете объект, такой как,
он помещается в стек.
Вы всегда должны будете вызывать уничтожение на объекте, который вы поместили в кучу с новым. Это открывает возможность для утечек памяти. Объекты, помещенные в стек, не подвержены утечке памяти!
источник
std::string
илиstd::map
, да, острое понимание. Моей первоначальной реакцией было «но также очень часто отделить время жизни объекта от области действия создаваемого кода», но на самом делеconst
лучше возвращать по значению или принимать значения в области вызывающего абонента без ссылки или указателя, за исключением случаев, когда требуется «расширение» тоже. Хотя есть и другие способы использования звука, например, заводские методы ...Одна заметная причина, по которой следует избегать чрезмерного использования кучи, связана с производительностью, особенно с производительностью механизма управления памятью по умолчанию, используемого C ++. В то время как распределение может быть довольно быстро в тривиальном случае, делать много
new
иdelete
на объектах неравномерного размера без строгого порядка приводит не только к фрагментации памяти, но это также усложняет алгоритм распределения и может абсолютно уничтожить производительность в некоторых случаях.Это проблема, которую пулы памяти создавали для решения, позволяя смягчить недостатки, присущие традиционным реализациям кучи, и в то же время позволять использовать кучи по мере необходимости.
Тем не менее, лучше вообще избежать этой проблемы. Если вы можете положить его в стек, то сделайте это.
источник
Я думаю, что плакат хотел сказать,
You do not have to allocate everything on the
heap
а неstack
.В основном объекты размещаются в стеке (если, конечно, размер объекта позволяет) из-за дешевой стоимости размещения в стеке, а не распределения на основе кучи, которое требует довольно большой работы со стороны распределителя и добавляет многословность, потому что тогда вам нужно управлять данными, размещенными в куче.
источник
Я склонен не соглашаться с идеей использования нового «слишком много». Хотя использование оригинального плаката новых с системными классами немного нелепо. («На
int *i; i = new int[9999];
самом деле»int i[9999];
гораздо понятнее.) Я думаю, что именно это и получило козу комментатора.Когда вы работаете с системными объектами, это очень редко вам понадобится более одной ссылки на один и тот же объект. Пока значение одинаково, это все, что имеет значение. А системные объекты обычно не занимают много места в памяти. (один байт на символ в строке). И если они это сделают, библиотеки должны быть спроектированы так, чтобы учитывать это управление памятью (если они хорошо написаны). В этих случаях (все, кроме одной или двух новостей в его коде) новость практически бессмысленна и служит только для введения в заблуждение и потенциальной возможности ошибок.
Однако, когда вы работаете со своими собственными классами / объектами (например, классом Line исходного автора), вы должны сами начать думать о таких проблемах, как объем памяти, постоянство данных и т. Д. На этом этапе разрешение нескольких ссылок на одно и то же значение неоценимо - оно позволяет создавать такие конструкции, как связанные списки, словари и графики, где несколько переменных должны не только иметь одно и то же значение, но и ссылаться на один и тот же объект в памяти. Однако класс Line не имеет ни одного из этих требований. Таким образом, исходный код плаката фактически не нуждается в
new
.источник
When you're working with your own classes/objects
... у вас часто нет причин делать это! Крошечная доля Qs находится на деталях конструкции контейнера опытными программистами. В противоположность этому , угнетающее доля в о путанице новичков , которые не знают , что STDLIB существует - или активно данной жуткие задания в «программирования» «курсы», где учитель требует они бесцельно изобретать колесо - прежде , чем они даже узнал что такое колесо и почему оно работает. Продвигая более абстрактное распределение, C ++ может спасти нас от бесконечного «segfault со связанным списком» C; пожалуйста, давай позволим .int *i; i = new int[9999];
Действительно? »int i[9999];
намного яснее.)« Да, это более понятно, но, играя в адвокат дьявола, этот тип не обязательно является плохим аргументом. С 9999 элементами я могу представить себе жесткую встроенную систему, в которой недостаточно стека для 9999 элементов: 9999x4 байта составляет ~ 40 кБ, x8 ~ 80 кБ. Таким образом, такие системы, возможно, должны использовать динамическое распределение, предполагая, что они реализуют его, используя альтернативную память. Тем не менее, это может только оправдать динамическое распределение, а неnew
; в этом случаеvector
было бы настоящим исправлениемstd::make_unique<int[]>()
конечно).Две причины:
delete
этом позже, или это приведет к утечке памяти.источник
new
это новоеgoto
.Вспомните, почему
goto
так поносили: хотя это мощный низкоуровневый инструмент для управления потоками, люди часто использовали его неоправданно сложными способами, которые затрудняли выполнение кода. Кроме того, наиболее полезные и удобные для чтения шаблоны были закодированы в операторах структурированного программирования (например,for
илиwhile
); конечный эффект заключается в том, что код, в которомgoto
есть подходящий способ, довольно редок, если у вас возникает соблазн писатьgoto
, вы, вероятно, делаете что-то плохо (если вы действительно не знаете, что делаете).new
похож - он часто используется, чтобы сделать вещи излишне сложными и трудными для чтения, и наиболее полезные шаблоны использования могут быть закодированы в различные классы. Кроме того, если вам нужно использовать какие-либо новые шаблоны использования, для которых еще нет стандартных классов, вы можете написать свои собственные классы, которые их кодируют!Я бы даже утверждать , что
new
это хуже , чемgoto
, в связи с необходимостью парыnew
иdelete
отчетности.подобно
goto
, если вы когда-либо думаете, что вам нужно использоватьnew
, вы, вероятно, делаете что-то плохо, особенно если вы делаете это вне реализации класса, целью которого в жизни является инкапсуляция любых динамических распределений, которые вам нужно сделать.источник
Основная причина в том, что объекты в куче всегда сложнее использовать и управлять, чем простые значения. Написание кода, который легко читать и поддерживать, всегда является приоритетом любого серьезного программиста.
Другой сценарий - библиотека, которую мы используем, обеспечивает семантику значений и делает ненужным динамическое размещение.
Std::string
хороший примерОднако для объектно-ориентированного кода использование указателя - что означает использование
new
для его предварительного создания - обязательно. Чтобы упростить управление ресурсами, у нас есть десятки инструментов, которые делают его максимально простым, например, умные указатели. Объектно-ориентированная парадигма или универсальная парадигма предполагает семантику значений и требует меньше или не требуетnew
, так же как и постеры в других местах.Традиционные шаблоны проектирования, особенно те, которые упоминаются в книге GoF , часто используют
new
, поскольку они являются типичным ОО-кодом.источник
For object oriented code, using a pointer [...] is a must
: Нонсенс . Если вы обесцениваете «OO», ссылаясь только на небольшое подмножество, полиморфизм - тоже нонсенс: ссылки тоже работают.[pointer] means use new to create it beforehand
: особенно бессмыслица : ссылки или указатели могут быть взяты на автоматически распределенные объекты и использованы полиморфно; следить за мной .[typical OO code] use new a lot
Может быть, в какой-то старой книге, но кого это волнует? Любые смутно современные C ++ избегаютnew
/ необработанных указателей везде, где это возможно - и ни в коем случае не являются чем-то вроде OO, делая этоЕще один момент ко всем приведенным выше правильным ответам, это зависит от того, какое программирование вы делаете. Например, разработка ядра в Windows -> Стек сильно ограничен, и вы не сможете воспринимать ошибки страницы, как в режиме пользователя.
В таких средах новые или C-подобные вызовы API предпочтительны и даже необходимы.
Конечно, это всего лишь исключение из правил.
источник
new
размещает объекты в куче. В противном случае объекты размещаются в стеке. Посмотрите на разницу между ними .источник