2009-12-16 2 views
23

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

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

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

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

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

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

+6

Для всех ответчиков: вопрос заключается в том, как это может привести к ** только ** утечке памяти, то есть как это возможно ** не ** привести к повреждению кучи. – Thomas

+2

Довольно легко. Все зависит от того, как написано управление памятью. Поскольку это не определено стандартом, все ответы - это просто предположение (но я уверен, что я мог бы написать версию, которая бы не разбивала кучу, а делала утечку памяти). Подсистема управления памятью работает как можно быстрее и эффективнее. Стандарт дал им набор предварительных и пост-условий, при которых подсистема может быть оптимизирована. Разбейте эти условия, и у вас есть неопределенное поведение (возможно, повреждение кучи). В отладочном состоянии стабильность не является целью подсистемы памяти. Следовательно, утечка более вероятна. –

+0

http://stackoverflow.com/questions/1553382/pod-freeing-memory-is-delete-equal-to-delete/1553407#1553407 – sbi

ответ

25

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

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

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

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

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

+0

Недурно. Как вы только что сказали, OP предполагает, что новые и новые [] обрабатываются по-разному, но это может быть не так. «new» может быть просто «new []» с size_t, добавленным w/значением 1. –

+0

Я фактически имел в виду «размер», чтобы указать количество байтов, а не элементов. Что-то, что могла бы выполнять функция типа malloc. Я немного отредактирую свой пост, чтобы сделать это явным. – Thomas

+3

Если эта технология управления памятью использовалась. НО тогда у вас есть накладные расходы на x байтов для хранения размера, что на 100% больше для небольших объектов. Да, мы могли бы заплатить эту стоимость, если бы хотели компенсировать плохих программистов. Но я не хочу платить эту цену только за поддержку «sharptooth», поэтому я бы предпочел, чтобы управление памятью было очень эффективным (даже для небольших типов). В результате стандарт не требует, и большинство реализаций не добавляют размер для нового в версии выпуска. Хотя некоторые из них в отладочной версии просто помогают в отладке/профилировании. –

4

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

3

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

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

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: обратите внимание, что конструкторы не вызываются во многих других ошибок программирования, тоже: невиртуальных деструкторов базового класса , ложная зависимость от умных указателей, ...

+0

@Suma: проблема, которую я попытался показать здесь, заключается в том, что только деструктор вызывается первый объект, в результате чего происходит 9 пропущенных блоков, содержащих 1 'char'. Вы правы в массиве элементов 'A', но это не вопрос. – xtofl

+0

@ Сьюма: никакого вреда в том, что объяснение было немного скрыто. Спасибо за то, что критически, мы это сделали! – xtofl

3

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

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

+1

-1 для «в некоторых случаях» Профессиональный ответ должен указывать на ссылку. например Пункт 5 Эффективного C++ С.Мейера: «Что произойдет, если вы использовали [] ... Результат не определен. ... он не определен даже для встроенных типов ... Правило тогда просто: if вы используете [] при вызове new, вы должны использовать [] при вызове delete. Если вы не используете [] при вызове new, не используйте [] при вызове delete. " –

3

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

6

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

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

8

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

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

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

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

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

2

В конце ответа, но ...

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

2

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

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

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

HENCE: Неопределенное поведение.

+0

Да, это может привести к обоим, но IMO, если у вас есть куча, вы больше не заботитесь о утечке памяти. – sharptooth

+0

Точка в том, что она не определена. Не имеет значения, какой известный ответ подходит для большинства компиляторов. Если случайно есть один компилятор, который реализует новые и новые [], удаляет и удаляет [] точно в одинаковых манерах, никогда не будет кучи. Поэтому ответ на вопрос заключается в том, что, если компилятор реализует таким образом, чтобы избежать кучи, он может вызвать утечку памяти. За исключением случаев, когда компилятор реализует таким образом, чтобы избежать обоих. Поэтому бессмысленно отвечать на такой вопрос, если мы не будем ссылаться на конкретный компилятор. –

1

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

Просто небольшая вещь сразу гугл: http://en.cppreference.com/w/cpp/memory/new/operator_delete

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

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

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

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

классный рассказ, утечка памяти, почему меня это волнует?

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

Смежные вопросы