Висячий указатель - Dangling pointer - Wikipedia

Висячий указатель

Свисающие указатели и дикие указатели в компьютерное программирование находятся указатели которые не указывают на действительный объект соответствующего типа. Это частные случаи безопасность памяти нарушения. В более общем смысле, висячие ссылки и дикие ссылки находятся Рекомендации которые не ведут к действительному пункту назначения и включают такие явления, как ссылка гниль в Интернете.

Свисающие указатели возникают во время разрушение объекта, когда объект, имеющий входящую ссылку, удаляется или освобождается без изменения значения указателя, так что указатель по-прежнему указывает на ячейку освобожденной памяти. Система может перераспределить ранее освобожденную память, и если программа затем разыменования (сейчас) висячий указатель, непредсказуемое поведение может привести, так как память теперь может содержать совершенно другие данные. Если программа выполняет запись в память, на которую ссылается висячий указатель, может произойти тихое повреждение несвязанных данных, что приведет к незаметному ошибки это может быть чрезвычайно сложно найти. Если память была перераспределена другому процессу, то попытка разыменования висячего указателя может вызвать ошибки сегментации (UNIX, Linux) или общие неисправности защиты (Windows). Если у программы есть достаточные привилегии, позволяющие ей перезаписывать бухгалтерские данные, используемые распределителем памяти ядра, повреждение может вызвать нестабильность системы. В объектно-ориентированные языки с вывоз мусора висячие ссылки предотвращаются только путем уничтожения недоступных объектов, то есть у них нет входящих указателей; это обеспечивается либо отслеживанием, либо подсчет ссылок. Однако финализатор может создавать новые ссылки на объект, требующие воскрешение объекта чтобы предотвратить висящую ссылку.

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

Причина висящих указателей

На многих языках (например, Язык программирования C ) явное удаление объекта из памяти или уничтожение кадр стека при возврате не изменяет связанные указатели. Указатель по-прежнему указывает на то же место в памяти, хотя теперь его можно использовать для других целей.

Ниже показан простой пример:

{   char *дп = НОЛЬ;   /* ... */   {       char c;       дп = &c;   }      / * c выходит за рамки * /     / * теперь dp - висячий указатель * /}

Если операционная система способна обнаруживать ссылки времени выполнения на нулевые указатели, решение вышеперечисленного - присвоить dp 0 (ноль) непосредственно перед выходом из внутреннего блока. Другое решение - каким-то образом гарантировать, что dp больше не будет использоваться без дальнейшей инициализации.

Еще один частый источник висячих указателей - это беспорядочная комбинация malloc () и свободный() вызовы библиотеки: указатель становится висящим, когда блок памяти, на который он указывает, освобождается. Как и в предыдущем примере, один из способов избежать этого - убедиться, что указатель сброшен на нуль после освобождения его ссылки, как показано ниже.

#включают <stdlib.h>пустота func(){    char *дп = маллок(A_CONST);    /* ... */    свободный(дп);         / * dp теперь становится висящим указателем * /    дп = НОЛЬ;        / * dp больше не болтается * /    /* ... */}

Слишком распространенной ошибкой является возврат адресов локальной переменной, выделенной стеком: как только вызываемая функция возвращается, пространство для этих переменных освобождается, и технически они имеют «мусорные значения».

int *func(пустота){    int число = 1234;    /* ... */    возвращаться &число;}

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

Освобождение вручную без зависшей ссылки

Антони Кречмар [pl ] (1945-1996) создал полную систему управления объектами, свободную от феномена висячих ссылок, см.[2]

Схема аксиом операции kill
Пусть x1, ... ,Иксп быть переменными, n> 0, 1≤i≤n. Каждая формула следующей схемы - это теорема о виртуальной машине, построенной Кречмаром.
читать как: если объект о это ценность п переменные, затем после выполнения инструкции убить (хя) общее значение этих переменных никто (это означает, что с этого момента объект о недоступен, и, следовательно, часть памяти, занимаемая им, может быть переработана с помощью той же операции kill без какого-либо ущерба).

Как следствие:

  • нет необходимости повторять операцию kill (x1), убить (x2), ...[3]
  • нет феномена висячая ссылка,
  • любая попытка доступа к удаленному объекту будет обнаружена и обозначена как исключение „ссылка ни на что”.

Примечание: стоимость убийства постоянна. .

Похожий подход предложили Фишер и ЛеБлан. [4] под именем Замки и ключи.

Причина диких указателей

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

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

int ж(int я){    char *дп;    / * dp - это дикий указатель * /    статический char *scp;  / * scp не является диким указателем:                        * статические переменные инициализируются значением 0                        * в начале и сохраняют свои значения с                        * последний звонок потом.                        * Использование этой функции может считаться плохим                        * стиль, если нет комментариев * /}

Бреши в безопасности, связанные с висячими указателями

Нравиться переполнение буфера ошибки, ошибки висячих / диких указателей часто становятся дырами в безопасности. Например, если указатель используется для создания виртуальная функция вызов, другой адрес (возможно, указывающий на код эксплойта) может быть вызван из-за vtable указатель перезаписывается. В качестве альтернативы, если указатель используется для записи в память, некоторая другая структура данных может быть повреждена. Даже если память читается только после того, как указатель становится висящим, это может привести к утечке информации (если интересные данные помещаются в следующую структуру, размещенную там) или к повышение привилегий (если теперь недействительная память используется в проверках безопасности). Когда висячий указатель используется после того, как он был освобожден, без выделения ему нового фрагмента памяти, это становится известным как уязвимость «использования после освобождения».[5] Например, CVE -2014-1776 - это уязвимость, которая используется после освобождения в Microsoft Internet Explorer 6–11.[6] используется атаки нулевого дня по сложная постоянная угроза.[7]

Как избежать ошибок висячих указателей

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

#включают <assert.h>#включают <stdlib.h>/ * Альтернативная версия для 'free ()' * /пустота безопасный(пустота **pp){    / * в режиме отладки, прервать, если pp равен NULL * /    утверждать(pp);    если (pp != НОЛЬ) {               / * проверка безопасности * /        свободный(*pp);                  / * освободить кусок, обратите внимание, что free (NULL) действителен * /        *pp = НОЛЬ;                 / * сбросить исходный указатель * /    }}int ж(int я){    char *п = НОЛЬ, *p2;    п = маллок(1000);    / * получаем кусок * /    p2 = п;              / * копируем указатель * /    / * используйте здесь чанк * /    безопасный((пустота **)&п);       / * снятие безопасности; не влияет на переменную p2 * /    безопасный((пустота **)&п);       / * этот второй вызов не потерпит неудачу * /    char c = *p2;       / * p2 по-прежнему является висячим указателем, так что это неопределенное поведение. * /    возвращаться я + c;}

Альтернативная версия может использоваться даже для гарантии действительности пустого указателя перед вызовом malloc ():

    безопасный(&п);        / * я не уверен, был ли выпущен чанк * /    п = маллок(1000);    / * выделить сейчас * /

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

Среди более структурированных решений популярным методом предотвращения висячих указателей в C ++ является использование умные указатели. Умный указатель обычно использует подсчет ссылок вернуть объекты. Некоторые другие методы включают надгробия метод и замки и ключи метод.[4]

Другой подход - использовать Сборщик мусора Boehm, консервативный уборщик мусора который заменяет стандартные функции распределения памяти в C и C ++ со сборщиком мусора. Этот подход полностью устраняет ошибки висячих указателей за счет отключения освобождения и восстановления объектов путем сборки мусора.

В таких языках, как Java, висячие указатели не могут возникать, потому что нет механизма для явного освобождения памяти. Скорее, сборщик мусора может освободить память, но только тогда, когда объект больше не доступен по каким-либо ссылкам.

На языке Ржавчина, то система типов был расширен, чтобы включить также время жизни переменных и получение ресурсов - это инициализация. Если не отключить функции языка, висячие указатели будут обнаружены во время компиляции и сообщены как ошибки программирования.

Обнаружение зависшего указателя

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

Некоторые отладчики автоматически перезаписывают и уничтожают данные, которые были освобождены, обычно с определенным шаблоном, например 0xDEADBEEF (Отладчик Microsoft Visual C / C ++, например, использует 0xCC, 0xCD или же 0xDD в зависимости от того, что было освобождено[8]). Обычно это предотвращает повторное использование данных, делая их бесполезными, а также очень заметными (шаблон служит, чтобы показать программисту, что память уже освобождена).

Такие инструменты как Polyspace, TotalView, Валгринд, Брызговик,[9] AddressSanitizer, или инструменты на основе LLVM[10] также может использоваться для обнаружения использования висячих указателей.

Другие инструменты (SoftBound, Страхование ++, и CheckPointer ) инструментирует исходный код для сбора и отслеживания допустимых значений указателей («метаданных») и проверки достоверности каждого доступа к указателю по метаданным.

Другая стратегия при подозрении на небольшой набор классов - временно сделать все их функции-члены виртуальный: после того, как экземпляр класса был уничтожен / освобожден, его указатель на Таблица виртуальных методов установлен на НОЛЬ, и любой вызов функции-члена приведет к сбою программы и покажет виновный код в отладчике.

Другое использование

Период, термин висячий указатель может также использоваться в других контекстах, кроме программирования, особенно техническими специалистами. Например, номер телефона человека, который с тех пор сменил телефон, является реальным примером висящего указателя.[11] Другой пример - запись в онлайн-энциклопедия это относится к другой записи, заголовок которой был изменен, заменяя любые ранее существовавшие ссылки на эту запись на висячие указатели.

Смотрите также

Рекомендации

  1. ^ https://gcc.gnu.org/onlinedocs/gcc-4.0.2/gcc/Warning-Options.html
  2. ^ Джанна Чиони, Антони Кречмар, Запрограммированное освобождение без зависшей ссылки, Письма об обработке информации, т. 18, 1984, стр 179-185
  3. ^ В C ++ помещая инструкции delete (x1,); ... удалить (xп); это единственный способ избежать ошибки висящего указателя.
  4. ^ а б C.N. Фишер, Р.Дж. Леблан, Реализация диагностики во время выполнения на Паскале , IEEE Trans. Софтв. Eng., 6 (4): 313-319, 1980.
  5. ^ Дальчи, Эрик; анонимный автор; CWE Content Team (11 мая 2012 г.). «CWE-416: использовать после бесплатного». Перечень общих слабых мест. Mitre Corporation. Получено 28 апреля, 2014.
  6. ^ "CVE-2014-1776". Общие уязвимости и уязвимости (CVE). 2014-01-29. Архивировано из оригинал на 2017-04-30. Получено 2017-05-16.
  7. ^ Чен, Сяобо; Казелден, Дэн; Скотт, Майк (26 апреля 2014 г.). «Новый эксплойт нулевого дня, нацеленный на Internet Explorer версий с 9 по 11, обнаруженный в целевых атаках». Блог FireEye. FireEye. Получено 28 апреля, 2014.
  8. ^ Шаблоны заполнения памяти Visual C ++ 6.0
  9. ^ Отладка указателя брызговика
  10. ^ Дхурджати Д. и Адве В. Эффективное обнаружение всех видов использования висячих указателей на рабочих серверах
  11. ^ "Файл жаргона". версия 4.4.7. Получено 2014-01-07.