Подтвердить что ты не робот

Что происходит при двойном удалении?

Obj *op = new Obj;
Obj *op2 = op;
delete op;
delete op2; // What happens here?

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

4b9b3361

Ответ 1

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

Ответ 2

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

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

Ответ 3

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

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

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

Посмотрите туда, gcc runtime фактически обнаруживает, что он был двойным удалением и довольно полезен, прежде чем он сработает.

Ответ 4

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

Ответ 5

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

Из С++ FAQ: посетите эту ссылку

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

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

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

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

Ответ 6

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

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

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

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

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

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

Итак, если ваш деструктор содержит какой-либо код, который обращается к любым данным класса, который уже был удален, он может segfault OR (скорее всего) вы прочтете некоторые бессмысленные данные. Если эти удаленные данные являются указателями, то это, скорее всего, будет 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. Но может произойти что-то еще хуже.

Собирается ли компилятор сбросить ошибку?

Это не должно быть.

Ответ 7

Пока это undefined:

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!

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