Каким образом соединение new [] с delete могло привести только к утечке памяти?

Прежде всего, использование delete для всего, что выделено с помощью new[], является неопределенным поведением в соответствии со стандартом C ++.

В Visual C ++ 7 такое объединение может привести к одному из двух последствий.

Если тип new [] 'ed имеет тривиальный конструктор и деструктор, VC ++ просто использует new вместо new[] и использование delete для этого блока работает нормально - new просто вызывает «выделить память», delete просто вызывает «свободную память».

Если тип new [] 'ed имеет нетривиальный конструктор или деструктор, описанный выше трюк не может быть выполнен - ​​VC ++ 7 должен вызывать точно нужное количество деструкторов. Таким образом, он добавляет к массиву size_t, в котором хранится количество элементов. Теперь адрес, возвращаемый new[], указывает на первый элемент, а не на начало блока. Таким образом, если используется delete, он вызывает только деструктор для первого элемента и вызывает «свободную память» с адресом, отличным от адреса, возвращаемого функцией «выделить память», и это приводит к некоторому индикатору ошибки внутри HeapFree (), который, как я подозреваю, относится к куча коррупции.

Тем не менее, повсюду можно прочитать ложные утверждения, что использование delete после new[] приводит к утечке памяти. Я подозреваю, что любой размер повреждения кучи намного важнее, чем тот факт, что деструктор вызывается только для первого элемента, и, возможно, не вызванные деструкторы не освободили выделенные в куче подобъекты.

Каким образом использование delete после new[] могло привести только к утечке памяти в некоторых реализациях C ++?


person sharptooth    schedule 16.12.2009    source источник
comment
Для всех респондентов: вопрос в том, как это может привести к только утечке памяти, т.е. как это может не вызвать повреждение кучи.   -  person Thomas    schedule 16.12.2009
comment
Довольно легко. Все зависит от того, как написано управление памятью. Поскольку это не определено стандартом, все ответы - это просто предположения (но я уверен, что мог бы написать версию, которая не разрушала бы кучу, но имела бы утечку памяти). Подсистема управления памятью работает максимально быстро и эффективно. Стандарт предоставил им набор предварительных и дополнительных условий, при которых можно оптимизировать подсистему. Если нарушить эти условия, вы получите неопределенное поведение (возможно, повреждение кучи). При отладке целью подсистемы памяти является стабильность, а не скорость. Следовательно, утечка более вероятна.   -  person Martin York    schedule 16.12.2009
comment
stackoverflow.com/questions/1553382/   -  person sbi    schedule 12.01.2011


Ответы (10)


Предположим, я компилятор C ++ и реализую управление памятью следующим образом: я добавляю каждому блоку зарезервированной памяти размер памяти в байтах. Что-то вроде этого;

| size | data ... |
         ^
         pointer returned by new and new[]

Обратите внимание, что с точки зрения распределения памяти нет разницы между new и new[]: оба просто выделяют блок памяти определенного размера.

Как теперь delete[] узнать размер массива, чтобы вызвать нужное количество деструкторов? Просто разделите size блока памяти на sizeof(T), где T - это тип элементов массива.

Теперь предположим, что я реализую delete как просто один вызов деструктора с последующим освобождением size байтов, тогда деструкторы последующих элементов никогда не будут вызываться. Это приводит к утечке ресурсов, выделяемых последующими элементами. Тем не менее, поскольку я освобождаю size байтов (а не sizeof(T) байтов), никакого повреждения кучи не происходит.

person Thomas    schedule 16.12.2009
comment
Пальцы вверх. Как вы только что сказали, OP предполагает, что new и new [] обрабатываются по-разному, но это может быть не так. new может быть просто new [] с добавлением size_t со значением 1. - person Merlyn Morgan-Graham; 16.12.2009
comment
На самом деле я имел в виду size, чтобы указать количество байтов, а не элементов. То, что могла сделать функция типа malloc. Я немного отредактирую свой пост, чтобы сделать это явным. - person Thomas; 16.12.2009
comment
Если бы использовалась эта техника управления памятью. НО тогда у вас есть накладные расходы в размере x байтов для хранения размера, что на 100% больше для небольших объектов. Да, мы могли бы заплатить эту цену, если бы хотели компенсировать плохих программистов. Но я не хочу платить эту цену только за поддержку «острого зуба», поэтому я бы предпочел, чтобы управление памятью было очень эффективным (даже для небольших типов). В результате стандарт не требует, и большинство реализаций не добавляют размер для нового в окончательной версии. Хотя некоторые делают в отладочной версии только для помощи в отладке / профилировании. - person Martin York; 16.12.2009
comment
@Thomas: Да, но это очень искусственный, принудительный и никогда не применяемый на практике подход к реализации управления памятью. Это определенно не может служить объяснением того, как появилась популярная легенда об утечке памяти. - person AnT; 19.12.2009
comment
Это все хорошо, но если бы так было всегда, тогда не было бы необходимости в конструкции delete [], потому что библиотека времени выполнения была бы достаточно умной, чтобы вычислить, сколько объектов нужно освободить. Спецификация не требует выделения для использования какой-либо конкретной реализации, поэтому для всех практических целей поведение не определено, и его следует избегать. - person Mike Collins; 14.03.2017
comment
@MikeCollins Конечно, но вопрос был не в этом. - person Thomas; 15.03.2017

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

Наиболее вероятное объяснение этой чуши об "утечке памяти" состоит в том, что с невинно наивной точки зрения разница между delete и delete[] состоит в том, что delete используется для уничтожения только одного объекта, а delete[] уничтожает массив объектов ("множество" объектов) . Наивный вывод, который обычно делается из этого, состоит в том, что первый элемент массива будет уничтожен delete, а остальные останутся, таким образом создавая предполагаемую «утечку памяти». Конечно, любой программист, обладающий хотя бы базовым пониманием типичных реализаций кучи, сразу поймет, что наиболее вероятным последствием этого является повреждение кучи, а не «утечка памяти».

Другое популярное объяснение наивной теории «утечки памяти» состоит в том, что, поскольку вызывается неправильное количество деструкторов, вторичная память, принадлежащая объектам в массиве, не освобождается. Это может быть правдой, но очевидно, что это очень вынужденное объяснение, которое не имеет большого значения перед лицом гораздо более серьезной проблемы с повреждением кучи.

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

Излишне добавлять, что new/delete и new[]/delete[] на самом деле являются двумя независимыми механизмами управления памятью, которые можно настраивать независимо. После того, как они будут настроены (путем замены необработанных функций управления памятью), нет абсолютно никакого способа даже начать предсказывать, что может случиться, если они смешаются.

person AnT    schedule 19.12.2009

Похоже, действительно ваш вопрос: «Почему не бывает разрушения кучи?». Ответ на этот вопрос - «потому что диспетчер кучи отслеживает размеры выделенных блоков». Вернемся на минутку к C: если вы хотите выделить один int в C, вы должны сделать int* p = malloc(sizeof(int)), если вы хотите выделить массив размером n, вы можете написать int* p = malloc(n*sizeof(int)) или int* p = calloc(n, sizeof(int)). Но в любом случае вы освободите его до free(p), как бы вы его не распределили. Вы никогда не передаете размер в free (), free () просто «знает», сколько нужно освободить, потому что размер блока malloc () - ed сохраняется где-то «перед» блоком. Возвращаясь к C ++, new / delete и new [] / delete [] обычно реализуются в терминах malloc (хотя они и не должны быть таковыми, вам не следует полагаться на это). Вот почему комбинация new [] / delete не повреждает кучу - удаление освобождает нужный объем памяти, но, как объясняли все до меня, вы можете получить утечки, не вызывая нужное количество деструкторов.

Тем не менее, рассуждения о неопределенном поведении в C ++ всегда бессмысленны. Почему это важно, если комбинация new [] / delete работает, "только" утекает или вызывает повреждение кучи? Вы не должны так кодить, точка! И на практике я бы по возможности избегал ручного управления памятью - STL и ускорение существуют не просто так.

person sbk    schedule 16.12.2009

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

person Martin Liversage    schedule 16.12.2009

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

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

person Charles Eli Cheese    schedule 16.12.2009
comment
-1, потому что в некоторых случаях профессиональный ответ должен указывать на ссылку. например Правило 5 S.Meyers's Effective C ++: Что бы произошло, если бы вы использовали [] ... Результат не определен. ... он не определен даже для встроенных типов ... Таким образом, правило простое: если вы используете [] при вызове new, вы должны использовать [] при вызове delete. Если вы не используете [] при вызове new, не используйте [] при вызове delete. - person Valentin Heinitz; 02.11.2010

утечка памяти может произойти, если оператор new () переопределен, а new [] - нет. то же самое и с оператором delete / delete []

person YeenFei    schedule 16.12.2009

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

Это самый простой из возможных классов, который, как мне кажется, приводит к такому поведению:

 struct A { 
       char* ch;
       A(): ch( new char ){}
       ~A(){ delete ch; }
    };

A* as = new A[10]; // ten times the A::ch pointer is allocated

delete as; // only one of the A::ch pointers is freed.

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

person xtofl    schedule 16.12.2009
comment
@Suma: проблема, которую я попытался показать здесь, заключается в том, как вызывается только деструктор первого объекта, в результате чего утекают 9 блоков, содержащих 1 char. Вы правы насчет массива A элементов, но вопрос не в этом. - person xtofl; 09.03.2010
comment
@Suma: нет ничего плохого в том, чтобы указать, что объяснение было немного скрыто. Спасибо за критику, это нам нужно! - person xtofl; 09.03.2010

Поздно для ответа, но ...

Если ваш механизм удаления состоит в том, чтобы просто вызвать деструктор и поместить освобожденный указатель вместе с размером, подразумеваемым sizeof, в свободный стек, то вызов delete для фрагмента памяти, выделенного с помощью new [], будет результат теряется память - но не повреждение. Более сложные структуры malloc могут повредить или обнаружить такое поведение.

person Alice Purcell    schedule 16.03.2010

Почему не может быть ответа, что он вызывает и то, и другое?

Очевидно, утечка памяти происходит независимо от того, происходит ли повреждение кучи или нет.

Или, скорее, поскольку я могу повторно реализовать новое и удалить… Разве это не может вообще ничего не вызвать. Технически я могу заставить new и delete выполнять new [] и delete [].

Отсюда: неопределенное поведение.

person Lee Louviere    schedule 28.02.2011
comment
Да, это может вызвать и то, и другое, но ИМО, как только у вас есть повреждение кучи, вы больше не заботитесь об утечке памяти. - person sharptooth; 01.03.2011
comment
Дело в том, что это не определено. Для большинства компиляторов не имеет значения, какой ответ известен. Если случайно найдется один компилятор, который реализует new и new [], delete и delete [] совершенно одинаковыми способами, никогда не будет повреждения кучи. Следовательно, ответ на вопрос: если компилятор реализует такой способ, чтобы избежать повреждения кучи, это может вызвать только утечку памяти. За исключением случаев, когда компилятор реализует их таким образом, чтобы избежать того и другого. Поэтому отвечать на такой вопрос бессмысленно, если мы не говорим о конкретном компиляторе. - person Lee Louviere; 01.03.2011

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

Немного сразу с Google: http://en.cppreference.com/w/cpp/memory/new/operator_delete

Как бы то ни было, delete - это функция для одного объекта. Он освобождает экземпляр от указателя и уходит;

delete [] - это функция, используемая для освобождения массивов. Это означает, что он не просто освобождает указатель; Он объявляет весь блок памяти этого массива мусором.

На практике это все круто, но вы мне скажете, что ваше приложение работает. Вам, наверное, интересно ... почему?

Решение состоит в том, что C ++ не устраняет утечки памяти. Если вы используете delete без скобок, он удалит только массив как объект - процесс, который может вызвать утечку памяти.

классная история, утечка памяти, почему меня это должно волновать?

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

person A. Abramov    schedule 04.02.2015