Что должно произойти при использовании объекта после FreeAndNil?

В моем Delphi7 этот код

var MStr: TMemoryStream;
...
FreeAndNil(MStr);
MStr.Size:=0; 

генерирует AV: нарушение доступа по адресу 0041D6D1 в модуле «Project1.exe». Прочитайте адрес 00000000. Но кто-то настаивает, что он не должен выдвигать никаких исключений, несмотря ни на что. Он также говорит, что его Delphi 5 действительно не вызывает никаких исключений. Он называет это «устаревшей ошибкой указателя». Другими словами, он говорит, что FreeAndNil нельзя использовать в качестве отладчика для обнаружения двойной попытки освободить объект или использовать освобожденный объект.

Кто-нибудь может просветить меня? Должен ли это произойти с ошибкой (всегда / случайно) или программа должна выполнить эту ошибку без проблем?

Спасибо


Я спрашиваю об этом, потому что считаю, что в моей программе есть ошибка «двойной свободный объект» или «свободный доступ». Как заполнить память, выделенную объекту нулями, ПОСЛЕ того, как я освободил объект? Я хочу, чтобы таким образом, чтобы определить, где ошибка, путем получения и AV. Первоначально я надеялся, что если я установлю объект на FreeAndNil, я ВСЕГДА получу AV при попытке повторного доступа к нему.

12.12.2008 21:21:18
Я думаю, что «кому-то» не хватает некоторых важных понятий.
Toon Krijthe 12.12.2008 22:11:17
Как я уже говорил ранее, у FastMM есть опция, которая делает то, что вы описали. Роб указывает, что он перезапишет память «магическими числами», чтобы он мог обнаруживать ошибки такого рода. Взгляни на это. Загрузите полную версию FastMM4 и включите запись в файл. Я нашел это весьма полезным.
Vegar 14.12.2008 21:08:45
FreeAndNil () не обнуляет память, занятую экземпляром, а обнуляет переданную ссылку.
Bevan 30.12.2008 21:05:14
6 ОТВЕТОВ

Из того, что я вижу, этот код всегда должен приводить к ошибке. FreeAndNil явно устанавливает передаваемое значение в Nil (он же 0), поэтому вы обязательно должны получить нарушение прав доступа при попытке разыменования объекта.

10
12.12.2008 21:38:02

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

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

Это не ошибка устаревшего указателя. Это ошибка с нулевой ссылкой. Ошибка устаревшего указателя - это когда вы освободили объект, но не очистили все переменные, которые на него ссылались. Тогда переменная все еще содержит старый адрес объекта. Это очень трудно обнаружить. Вы можете получить такую ​​ошибку как это:

MStr := TMemoryStream.Create;
MStr.Free;
MStr.Size := 0;

Вы также можете получить такой, как это:

MStr := TMemoryStream.Create;
OtherStr := MStr;
FreeAndNil(MStr);
OtherStr.Size := 0;

Использование MStr.Sizeпосле того, как вы освободили указанный объект, MStrявляется ошибкой, и это должно вызвать исключение. Является ли это действительно вызывает исключение , зависит от реализации. Может, так и будет, а может и нет. Это не случайно, хотя.

Если вы ищете ошибку двойного освобождения, вы можете использовать средства отладки, которые предоставляет FastMM, как и другие предлагали. Он работает, фактически не освобождая память обратно операционной системе или даже обратно во внутренний пул свободной памяти Delphi. Вместо этого он записывает заведомо неверные данные в область памяти объекта, поэтому, когда вы увидите эти значения, вы узнаете, что читаете из того, что уже освободили. Он также изменяет VMT объекта, так что в следующий раз, когда вы вызовете виртуальный метод для ссылки на этот объект, вы получите предсказуемое исключение и даже скажет вам, какой предположительно освобожденный объект вы пытались использовать. Когда вы снова попытаетесь освободить объект, он может сказать вам не только, что вы уже освободили его, но также и то, где он был освобожден в первый раз (с трассировкой стека) и где он был выделен.меньше одного раза вместо большего.

Есть также привычки, которые вы можете использовать, чтобы избежать проблемы для будущего кода:

  • Сократите использование глобальных переменных. Глобальная переменная может быть изменена любым кодом в программе, заставляя вас задаться вопросом, когда вы ее используете: «Значение этой переменной все еще допустимо, или какой-то другой код уже освободил его?» Когда вы ограничиваете область действия переменной, вы уменьшаете объем кода, который необходимо учитывать в вашей программе, когда вы ищете причины, по которым переменная не имеет ожидаемого значения.
  • Быть ясно о том, кто владеет объектом. Когда есть две части кода, которые имеют доступ к одному и тому же объекту, вам необходимо знать, какой из этих частей кода владеет объектом. У каждого из них может быть своя переменная для ссылки на объект, но там есть только один объект. Если один фрагмент кода вызывает FreeAndNilсвою переменную, это все равно оставляет переменную другого кода неизменной. Если этот другой код считает, что он владеет объектом, то у вас проблемы. (Это понятие владельца не обязательно связано со TComponent.Ownerсвойством. Не должен быть объект , которому он принадлежит; это может быть общая подсистема вашей программы.)
  • Не храните постоянные ссылки на объекты, которыми вы не владеете. Если вы не сохраняете долгоживущие ссылки на объект, вам не нужно беспокоиться о том, действительны ли эти ссылки. Единственная постоянная ссылка должна быть в коде, который владеет объектом. Любой другой код, который должен использовать этот объект, должен получить ссылку в качестве входного параметра, использовать объект, а затем отбросить ссылку, когда он вернет свой результат.
21
13.12.2008 02:27:22

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

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

FastMM4 имеет некоторые настройки, которые вы можете использовать во время отладки, чтобы обнаружить такие условия. Из FsatMM4Options.inc:

{Установите следующую опцию, чтобы выполнить расширенную проверку всех блоков памяти. Все блоки дополняются заголовком и трейлером, которые используются для проверки целостности кучи. Освобожденные блоки также очищаются, чтобы гарантировать, что они не могут быть повторно использованы после освобождения. Эта опция значительно замедляет операции с памятью и должна использоваться только для отладки приложения, которое перезаписывает память или повторно использует свободные указатели. Установка этого параметра автоматически включает CheckHeapForCorruption и отключает ASMVersion. Очень важно: если вы включите эту опцию, вашему приложению потребуется библиотека FastMM_FullDebugMode.dll. Если эта библиотека недоступна, вы получите сообщение об ошибке при запуске.}
{$ Define FullDebugMode}

Еще одна цитата из того же файла:

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

Поскольку Delphi использует FastMM из Delphi 2007 (2006?), Вы должны получить ошибку, если попытаетесь удвоить объект.

5
12.12.2008 21:48:11
Free и nil также освобождают объект, поэтому другой указатель на него также не будет работать.
Toon Krijthe 12.12.2008 22:13:22
Это правда для более старых версий Delphi и классического менеджера памяти, или только для Delphi 2007+ с FastMM? Какова будет цель для комментария в FastMM4Options.inc, если то, что вы говорите, является правдой?
Vegar 12.12.2008 22:25:43
Gamecat, очень возможно, что повторное использование устаревшего (не нулевого) указателя не вызовет никаких исключений (и возможно, что оно может работать просто отлично.) Конечно, это плохо, и в тысячу раз лучше, если он не работает, но по крайней мере в Delphi 5 и 7 вы можете использовать устаревшие указатели.
Mihai Limbășan 12.12.2008 22:39:04
Это верно для всех версий. Комментарий FastMM означает, что освобожденные блоки нельзя использовать в полезных целях. Вы все еще можете прочитать, что находится в памяти, но это будут заведомо плохие значения, которые FastMM записал туда вместо значений, которые были в объекте ранее.
Rob Kennedy 13.12.2008 17:52:00
Муча, вы можете разыменовать устаревшие указатели во всех версиях. Это не значит, что вы получите правильные значения. С отключенными функциями отладки FastMM он может перераспределить освобожденную память для чего-то другого. Ваш устаревший указатель получит эти данные вместо предыдущих значений объекта.
Rob Kennedy 13.12.2008 17:53:34

Просто чтобы усложнить вопрос:

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

Причина этого заключается в том, что нарушение доступа вызвано разыменованием собственного указателя (в данном случае NIL), но это происходит только при доступе к полю или VMT объекта для вызова виртуального метода.

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

8
12.12.2008 22:15:36

Томас Мюллер : вы пробовали методы виртуального класса? Конструктор - это своего рода виртуальный метод, но вы вызываете его для типа, а не для экземпляра. Это означает, что даже некоторые конкретные виртуальные методы не будут вызывать AV при нулевой ссылке: D

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

1
30.12.2008 21:00:26
Вызов метода виртуального класса для переменной экземпляра не удастся. Конструкторы не имеют к этому никакого отношения.
Rob Kennedy 12.01.2009 09:56:45

Блог EurekaLog опубликовал отличную статью в апреле 2009 года:

Почему вы всегда должны использовать FreeAndNil вместо Free.

1
28.01.2010 19:13:47