2008-12-12 4 views
8

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

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

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

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

Благодаря


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

+2

Я думаю, что «кто-то» не хватает некоторых важных понятий. – 2008-12-12 22:11:17

+1

Как я уже говорил, у FastMM есть опция, которая делает то, что вы описываете. Роб указывает, что он перезапишет память «магическими числами», чтобы обнаружить такие ошибки. Взгляните на это. Загрузите полный FastMM4 и включите ведение журнала в файл. Я нашел это весьма полезным. – Vegar 2008-12-14 21:08:45

+2

FreeAndNil() не заполняет память, занятую экземпляром, она игнорирует переданную ссылку. – Bevan 2008-12-30 21:05:14

ответ

9

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

21

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

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

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

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

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

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

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

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

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

8

Просто усложнять вопрос:

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

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

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

1

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

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

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