Как обнаружить "оборванных указателей", если "Assigned()" не может этого сделать?

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

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

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

Ответ 1

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

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

Ответ 2

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

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

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

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

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

Ответ 3

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

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

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

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

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

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

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

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

Ответ 4

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

Ответ 5

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

Ответ 6

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

  • нет различия между объектом и ссылкой на объект. Для "нормальных" переменных, скажем, скаляра (например, int) или записи, эти два варианта использования можно хорошо TSomeRec - есть либо тип 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...