Что определяет, что записывается в указатель C ++, когда вызывается delete? - PullRequest
8 голосов
/ 07 мая 2009

У меня есть указатель на данный класс. Скажем, например, указатель:

0x24083094

Указатель указывает на:

0x03ac9184

Какая таблица виртуальных функций моего класса. Это имеет смысл для меня. В windbg все выглядит правильно.

Я удаляю указанный указатель. Сейчас на 0x24083094 стоит:

0x604751f8

Но это не какой-то случайный мусор, этот адрес вставляется туда каждый раз, это последовательно 0x604751f8! Настолько, что я действительно могу использовать этот адрес, чтобы определить, был ли этот указатель удален между выполнениями моего приложения!

Но почему? Как он определяет, что 0x604751f8 должно быть написано там?

Для справки, я использую окна, построенные под visual studio 2003.

Я знаю, что не могу полагаться на то, что это значение установлено, даже если оно выглядит согласованным, но могу ли я рассчитывать на то, что оно будет другим? Т.е., 0x03ac9184 не будет в 0x24083094, если указатель удален, верно? Что там положено? Это может быть что угодно, но 0x03ac9184 определенно не будет (или я мог бы по-прежнему вызывать методы, поскольку это таблица виртуальных функций). Я прав?

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

По сути, я пытаюсь определить, что я нахожусь в этой ситуации, поэтому я могу элегантно выйти из своей функции. Я полагаю, что самый простой и лучший способ - просто выяснить, кому на самом деле принадлежит этот указатель, и спросить его, изменилось ли что-нибудь. Так что я собираюсь реализовать такое исправление. Он избегает любого из этих C ++ delete hacker-y, которые я обсуждал.

Однако , интересно то, что в нашем коде у нас есть класс BogusObject, который, по сути, действует как трей, ловящий людей, которые случайно разыменовывают освобожденные объекты. По сути, мы подключаем наши собственные функции удаления и помещаем класс BogusObject в виртуальную таблицу любого освобожденного класса.

Тогда, если кто-то что-то называет, он получает приятное сообщение, в котором говорится что-то вроде «эй, что-то не так, чувак». Это происходит в моем случае. То есть 0x604751f8+(someoffset) находится внутри класса BogusObject. Но мы больше не используем BogusObject! Он буквально нигде не настроен (даже ссылки должным образом, если я полностью удаляю класс BogusObject), и все же я получаю хорошее сообщение о том, что что-то не так! Но я сейчас придерживаюсь мнения, что это совпадение.

По какой-то причине среда выполнения помещает это значение 0x604751f8 в этот указатель при его удалении, и это просто соответствует одному классу, который имеет целью перехватывать подобные ситуации!

Ответы [ 10 ]

16 голосов
/ 07 мая 2009

Ничто в стандарте не определяет, что там написано. Visual studio (по крайней мере, в режиме отладки) будет часто записывать дозорные значения повсеместно, чтобы помочь в раннем обнаружении ошибок.

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

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

В любом случае, вы НЕ ДОЛЖНЫ использовать это значение указателя для чего-либо, что вы хотите продолжать работать, если только вы не позвоните в Microsoft и не получите некоторую документацию, объясняющую, почему это значение, и убедитесь, что оно не изменится. , И даже тогда знайте, что ваш код теперь привязан к поведению одного компилятора. В C ++ доступ к нераспределенной памяти является неопределенным и злым.

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

4 голосов
/ 07 мая 2009

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

Что означает конкретная ценность, на которую вы смотрите? Может быть что угодно.

2 голосов
/ 07 мая 2009

Нет, вы не можете полагаться на его установку. Вы даже не можете полагаться на то, что он другой.

Менеджерам кучи MS-DOS часто разрешалось использовать освобожденную память до следующего вызова malloc. Новое и удаленное в ту эпоху называется malloc и free.

В наши дни большинство менеджеров кучи разумно возвращают память в ОС, а это значит, что вы даже не можете полагаться на то, что она читаема! Даже те, которые все еще позволяют это (glibc имеет режим bwd-compat, который позволяет это), вы подвержены условиям гонки потоков.

Кроме того, удаление может изменить указатель на NULL, если это lvalue.

Когда вы вызываете delete, даже не думайте о разыменовании указателя.

2 голосов
/ 07 мая 2009

Программа пытается вам что-то сказать. Дата, номер телефона, кто знает?

Теперь серьезно, это не указано , полностью зависит от реализации , и, конечно, попытка разыменования этого указателя после delete приведет к неопределенному поведению. Короче, кого это волнует?

2 голосов
/ 07 мая 2009

Скорее всего, это значение указателя является vtable базового класса. Когда деструктор для производного класса запускается, после того как он завершает свое обычное тело, он «перезаписывает» память как базовый тип (в основном, записывая указатель vtable базового класса в объект), а затем вызывает деструктор базового класса.

Обратите внимание, что это поведение является внутренней деталью реализации поддержки C ++ во время исполнения компилятора, поэтому другие компиляторы (или будущие версии того же компилятора) могут делать что-то совершенно другое. Но это «преобразование vtable в базовый класс и вызов деструктора базового класса» довольно распространено и восходит к первоначальной реализации cfront в C ++

2 голосов
/ 07 мая 2009

Как сказал ранее Майкл Берр, память возвращается в бесплатный магазин. Некоторые бесплатные хранилища реализованы в виде связанных списков, а указатель -> next помещается в начало свободного буфера. Возможно, что магическое число, которое вы видите (0x604751f8), является защитником «конца списка». Вы можете проверить, выполнив следующий эксперимент:

Foo* f = new Foo();
Bar* b = new Bar();

// make a note of the values of f and b _pointers_

delete b;  // check that b points now to 0x604751f8
delete f;  // check that f points now to 0x604751f8

// now check that does b point to;  it might point to f!

Дайте нам знать, что вы найдете!

2 голосов
/ 07 мая 2009

Существует почти наверняка конкретное внутреннее значение для значения, которое появляется каждый раз, когда вы удаляете объект.

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

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

Постарайся выбросить это из головы!

2 голосов
/ 07 мая 2009

Как сказал Джош, есть несколько значений, которые можно вставить, чтобы облегчить жизнь отладчику с отладочными сборками. Они зависят от компилятора и никоим образом не могут быть использованы. Я полагаю, что в сборке релиза поведение большинства компиляторов C ++ по умолчанию заключается в том, чтобы ничего не делать с освобожденной памятью, поэтому до тех пор, пока это адресное пространство не будет выделено снова, содержимое будет в основном таким, каким оно было раньше, конечно, вы никогда не следует полагаться на это.

2 голосов
/ 07 мая 2009

Простой вызов оператора удаления для указателя не означает, что «удаленная» память будет очищена. Он будет вызывать только деструктор удаленных объектов и помечает выделенную память кучи как освобожденную. (это стандартное поведение оператора удаления).

Если вам нужно очистить содержимое памяти при удалении, вам нужно переопределить оператор удаления.

0 голосов
/ 14 мая 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...