2012-02-07 4 views
17
Obj *op = new Obj; 
Obj *op2 = op; 
delete op; 
delete op2; // What happens here? 

Что может быть самым худшим, если вы случайно удалите двойное удаление? Это имеет значение? Является ли компилятор ошибкой?Что происходит в двойном удалении?

+5

Да, это может иметь значение. Нет, компилятор не собирается «выкидывать ошибку». Просто не делай этого :) – paulsm4

+13

Я бы на практике подумал, что самое худшее, что может быть * правдоподобно, - это то, что через некоторое время распределитель памяти ошибочно работает.Это (a) заставляет вас потратить очень долгое время на его отладку, поскольку симптомы нигде не близки к этой причине и, кроме того, (b) создает серьезную уязвимость в программном обеспечении, которую ваш работодатель получает, , и ваш контракт заканчивается владельцем парня по имени Клайв, который делает разрешения на аренду по фиксированной цене за грузовик. Он заставляет вас работать с исправлением ошибок в вашем уведомлении о японских щупальцах. –

+0

Не делайте этого и никогда этого не делайте. Это неопределенное поведение. В таких случаях реализация C++ может сделать что угодно. На самом деле это может сделать плохие вещи, такие как повреждение вашей кучи, произвольные и причудливые изменения в других объектах в куче и могут делать даже самые плохие вещи. Если вы используете raw-указатели, лучше установить его как нулевой указатель после первого удаления, поскольку удаление нулевого указателя безопасно, в противном случае используйте интеллектуальные указатели в современном C++. – Destructor

ответ

19

Это вызывает неопределенное поведение. Все может случиться. На практике крах во время выполнения, вероятно, я ожидаю.

+16

Случайный щенок также может умереть. – Petr

+3

Известный случай освещения персонального монитора под огнем из-за неопределенного поведения. –

16

Это неопределенное поведение, поэтому фактический результат будет зависеть от среды выполнения компилятора &.

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

Под капотом любой менеджер памяти должен поддерживать некоторые метаданные каждого блока данных, который он выделяет, таким образом, чтобы он мог искать метаданные из указателя, возвращаемого malloc/new. Обычно это принимает форму структуры с фиксированным смещением перед выделенным блоком. Эта структура может содержать «магическое число» - константу, которая вряд ли произойдет по чистой случайности. Если диспетчер памяти видит магическое число в ожидаемом месте, он знает, что указатель, предоставленный для бесплатного/удаления, скорее всего, действителен. Если он не видит волшебное число или видит другое число, что означает, что «этот указатель был недавно освобожден», он может либо молча игнорировать бесплатный запрос, либо распечатать полезное сообщение и прервать его. Любой из них является законным по спецификации, и есть аргументы pro/con для любого подхода.

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

Давайте попробуем. Превратите свой код в полную программу в so.cpp:

class Obj 
{ 
public: 
    int x; 
}; 

int main(int argc, char* argv[]) 
{ 
    Obj *op = new Obj; 
    Obj *op2 = op; 
    delete op; 
    delete op2; 

    return 0; 
} 

Собирать (я использую GCC 4.2.1 на OSX 10.6.8, но YMMV):

[email protected] ~: g++ so.cpp 

запустить его:

[email protected] ~: ./a.out 
a.out(1965) malloc: *** error for object 0x100100080: pointer being freed was not allocated 
*** set a breakpoint in malloc_error_break to debug 
Abort trap 

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

+0

Также в случае, если есть вероятность, что может произойти «двойное удаление» - умный указатель обычно является лучшим решением. – paul23

+0

Хороший ответ, но я был бы счастливее, если бы вы сделали большую часть Неопределенного Поведения. –

+0

Я уверен, что другие ответы имеют «неопределенное поведение», хорошо освещенное. –

21

Неопределенное поведение. Нет никаких гарантий, сделанных стандартом. Вероятно, ваша операционная система дает некоторые гарантии, такие как «вы не испортите другой процесс», но это не очень помогает вашей программе.

Ваша программа может быть повреждена. Ваши данные могут быть повреждены. Прямой депозит вашей следующей зарплаты может вместо этого взять 5 миллионов долларов из вашей учетной записи.

+24

Или он мог заказать пиццу с вашей кредитной карточкой. –

+0

Может ли это исправить мою машину? – Goldname

3

Компилятор может дать предупреждение или что-то еще, особенно в очевидном (как в вашем примере), но это невозможно для его обнаружения. (Вы можете использовать что-то вроде valgrind, которое во время выполнения может его обнаружить). Что касается поведения, это может быть что угодно. Некоторая безопасная библиотека может проверить и обработать ее в порядке, но другие временные ряды (для скорости) сделают предположение, которое вы называете правильным (что это не так), а затем сбой или ухудшение.Среда выполнения позволяет сделать предположение, что вы не удваиваете удаление (даже если двойное удаление будет делать что-то плохое, например, сбой вашего компьютера)

0

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

С FAQ C++: посетить this link

Безопасно удалить тот же указатель дважды?
Нет! (. Предполагая, что вы не получите, что указатель назад от нового между ними)

Например, следующая катастрофа:

class Foo { /*...*/ }; 
void yourCode() 
{ 
    Foo* p = new Foo(); 
    delete p; 
    delete p; // DISASTER! 
    // ... 
} 

Это второе удаление р линии может сделать некоторые действительно плохие вещи к вам. Это может в зависимости от фазы луны испортить вашу кучу, свернуть вашу программу, сделать произвольные и причудливые изменения объектов, которые уже есть в куче, и т. Д. К сожалению, эти симптомы могут появляться и исчезать случайным образом. Согласно закону Мерфи, вы столкнетесь сильнее всего в самый худший момент (когда клиент ищет, когда транзакция с высокой стоимостью пытается опубликовать и т. Д.). Примечание: некоторые системы времени выполнения защитят вас от некоторых очень простых случаев двойного удаления. В зависимости от деталей вы можете быть в порядке, если вы работаете в одной из этих систем, и если никто никогда не развертывает ваш код в другой системе, которая обрабатывает вещи по-разному, и если вы удаляете что-то, что не имеет деструктора, и если вы не делаете ничего существенного между этими двумя удалениями, и если никто никогда не изменяет ваш код, чтобы сделать что-то существенное между этими двумя удалениями, и если ваш планировщик потоков (по которому вы, вероятно, не имеете никакого контроля!), не происходит, чтобы обменивать потоки между два удаляются, а если и если, и если. Вернемся к Мерфи: так как это может пойти не так, это произойдет, и в худшем возможном моменте это пойдет не так. Несоответствие не свидетельствует об отсутствии ошибки; он просто не может доказать наличие ошибки. Поверьте мне: двойное удаление плохо, плохо, плохо. Просто сказать нет.

0

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

Стандартный универсальный ответ: все может случиться, это не совсем так. Например, компьютер не будет пытаться убить вас за это (если вы не программируете AI для робота) :)

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

Но это то, что «примерно» происходит в большинстве случаев:

delete состоит из 2-х основных операций:

  • он вызывает деструктор, если он определен
  • это какое-то образом освобождает память, выделенную для объект

Итак, если ваш деструктор содержит любой код, который обращается к любым данным класса, который уже был удален, он может segfau lt ИЛИ (скорее всего) вы прочтете некоторые бессмысленные данные. Если эти удаленные данные являются указателями, то это, скорее всего, будет segfault, потому что вы попытаетесь получить доступ к памяти, которая содержит что-то еще или не принадлежит вам.

Если ваш конструктор не касается каких-либо данных или его нет (давайте не будем рассматривать виртуальные деструкторы здесь для простоты), это не может быть причиной сбоя в большинстве реализаций компилятора. Однако вызов деструктора - не единственная операция, которая будет происходить здесь.

Память должна быть бесплатной. Как это делается, зависит от реализации в компиляторе, но может также выполнить некоторую функцию free, указав на нее указатель и размер вашего объекта. Вызов free в уже удаленной памяти может привести к сбою, поскольку память больше не может принадлежать вам. Если он принадлежит вам, он может не сработать немедленно, но он может перезаписать память, которая уже была выделена для другого объекта вашей программы.

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

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

Вот пример код, который является неправильным, но может работать нормально, а (он хорошо работает с GCC на Linux):

class a {}; 

int main() 
{ 
    a *test = new a(); 
    delete test; 
    a *test2 = new a(); 
    delete test; 
    return 0; 
} 

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

*** Error in `./a.out': double free or corruption (fasttop): 0x000000000111a010 *** 

чтобы ответить на ваши вопросы прямо:

что самое худшее, что может случиться:

Теоретически, ваша программа вызывает что-то смертельное. Он может даже случайно попытаться стереть ваш жесткий диск в некоторых крайних случаях. Шансы зависят от вашей программы на самом деле (программа-драйвер ядра?).

На практике это, скорее всего, просто сбой с segfault. Но может случиться что-то еще хуже.

Разве компилятор сделает ошибку?

Не следует.

0

Хотя это не определено:

int* a = new int; 
delete a; 
delete a; // same as your code 

это определение:

int* a = new int; 
delete a; 
a = nullptr; // or just NULL or 0 if your compiler doesn't support c++11 
delete a; // nothing happens! 

Мысль я должен опубликовать его, так как никто не упоминал его.

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