Как обнаружить «висячие указатели», если «Assigned ()» не может это сделать? - PullRequest
14 голосов
/ 22 декабря 2011

В другом вопросе я обнаружил, что функция Assigned() идентична Pointer <> nil.Я всегда понимал, что Assigned() обнаруживает эти висячие указатели, но теперь я понял, что это не так.Dangling Pointers - это те, которые могли быть созданы в одной точке, но с тех пор были свободны и еще не были назначены на nil.

Если Assigned() не может обнаружить висячие указатели, точто можешь?Я хотел бы проверить свой объект, чтобы убедиться, что он действительно является действительным созданным объектом, прежде чем пытаться работать с ним.Я не использую FreeAndNil, как многие рекомендуют, потому что мне нравится быть прямым.Я просто использую SomeObject.Free.

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

Ответы [ 6 ]

14 голосов
/ 22 декабря 2011

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

Нарушения доступа не должны рассматриваться как враги. Они ошибки: они означают, что вы сделали ошибку, которую нужно исправить. (Или что в некотором коде, на который вы полагаетесь, есть ошибка, но я чаще всего обнаруживаю, что это я облажался, особенно когда имеешь дело с RTL, VCL или Win32 API.)

13 голосов
/ 22 декабря 2011

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

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

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

Вот почему Assigned () не является утверждением, что указатель действителен. Это просто проверяет, что указатель не ноль.

Почему Borland создал функцию Assigned () для начала? Для дальнейшего скрытия указателей от начинающих и случайных программистов. Вызовы функций легче читать и понимать, чем операции с указателями.

9 голосов
/ 22 декабря 2011

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

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

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

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

Конечно, мы все делаем ошибки и оставляем висячие указатели.Использование FreeAndNil - один из дешевых способов убедиться, что обнаружен висячий указатель.Более эффективным методом является использование FastMM в режиме полной отладки.Я не могу рекомендовать это достаточно высоко.Если вы не используете этот замечательный инструмент, вы должны начать делать это как можно скорее.

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

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

Мой прощальный выстрел: когда-либо пишите if Assigned, только если указатель имеет нормальное поведение nil.

7 голосов
/ 22 декабря 2011

Используйте диспетчер памяти, такой как FastMM, который обеспечивает поддержку отладки, в частности, для заполнения блока освобожденной памяти заданным байтовым шаблоном. Затем вы можете разыменовать указатель, чтобы увидеть, указывает ли он на блок памяти, начинающийся с байтового шаблона, или вы можете позволить коду нормально запускаться, а также поднимать AV, если он пытается получить доступ к освобожденному блоку памяти через висячий указатель. Сообщаемый адрес памяти AV обычно будет точно таким же или близким к байтовому шаблону.

6 голосов
/ 22 декабря 2011

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

0 голосов
/ 08 июля 2019

Суть в том, что способ реализации объектов в Delphi имеет некоторые встроенные недостатки дизайна:

  • нет различия между объектом и ссылкой на объект. Для "нормальных" переменных, скажем, скаляра (например, int) или записи, эти два варианта использования могут быть хорошо различены - есть либо тип Integer или TSomeRec, либо тип, подобный PInteger = ^Integer или PSomeRec = ^TSomeRec, которые являются разными типами. Это может звучать как пренебрежимая техническая составляющая, но это не так: SomeRec: TSomeRec обозначает «эта область является первоначальным владельцем этой записи и контролирует ее жизненный цикл », тогда как SomeRec: PSomeRec говорит «эта область использует временная ссылка на некоторые данные, , но не имеет никакого контроля над жизненным циклом записи . Поэтому, как бы глупо это ни звучало, для объектов практически нет ни одного , который бы обозначил контроль над другими объектами ' В результате - неожиданность - в некоторых ситуациях состояние объектов жизненного цикла может быть неясным.
  • ссылка на объект , простой указатель . В принципе, это нормально, но проблема в том, что есть много кода, который обрабатывает ссылки на объекты, как если бы они были 32-битными или 64-битными целыми числами. Так, например, если Embarcadero хотел изменить реализацию ссылки на объект (и сделать его уже не простым указателем), они бы сломали много кода .

Но если Embarcadero хочет устранить указатели на висящие объекты, им придется перепроектировать ссылки на объекты Delphi:

  • когда объект освобожден, все ссылки на него тоже должны быть освобождены. Это возможно только путем двойного связывания обоих, то есть экземпляр объекта должен нести список со всеми ссылками на него, то есть все адреса памяти, где находятся такие указатели (на самом низком уровне). После уничтожения этот список просматривается, и все эти указатели устанавливаются на nil
  • немного более удобным решением было то, что "один", содержащий такую ​​ссылку, может зарегистрировать обратный вызов, чтобы получить информацию, когда ссылочный объект уничтожен. В коде: когда у меня есть ссылка FSomeObject: TSomeObject, я хотел бы иметь возможность писать, например, в. SetSomeObject: FSomeObject.OnDestruction := Self.HandleDestructionOfSomeObject. Но тогда FSomeObject не может быть указателем; вместо этого должен быть хотя бы (расширенный) тип записи

Конечно, я могу реализовать все это сам, но это утомительно, и разве это не должно решаться самим языком? Им также удалось реализовать for x in ...

...