Как сопряжение new [] с delete может привести только к утечке памяти? - PullRequest
26 голосов
/ 16 декабря 2009

Прежде всего, использование 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 ++?

Ответы [ 10 ]

29 голосов
/ 16 декабря 2009

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

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

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

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

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

12 голосов
/ 19 декабря 2009

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

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

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

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

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

7 голосов
/ 16 декабря 2009

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

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

4 голосов
/ 16 декабря 2009

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

3 голосов
/ 16 марта 2010

Опоздал на ответ, но ...

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

3 голосов
/ 16 декабря 2009

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

3 голосов
/ 16 декабря 2009

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

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

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

2 голосов
/ 28 февраля 2011

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

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

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

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

1 голос
/ 05 февраля 2015

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

Просто мелочь, прямо с Google: http://en.cppreference.com/w/cpp/memory/new/operator_delete

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

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

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

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

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

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

...