Нулевой указатель
В вычислениях нулевой указатель или нулевая ссылка — это значение, сохраняемое для указания того, что указатель или ссылка не ссылается на действительный объект . Программы обычно используют нулевые указатели для представления таких условий, как конец списка неизвестной длины или невозможность выполнить какое-либо действие; такое использование нулевых указателей можно сравнить с типами, допускающими значение NULL , и со значением Nothing в типе параметра .
Нулевой указатель не следует путать с неинициализированным указателем : нулевой указатель гарантированно не равен любому указателю, указывающему на действительный объект. Однако, как правило, большинство языков не предоставляют такой гарантии. Он может сравниваться с другими действительными указателями; или он может сравниваться с нулевыми указателями. Это может произойти и в разное время; или сравнение может иметь неопределенное поведение . Также в языках, предлагающих такую поддержку, правильное использование инструментов линтера зависит от индивидуального опыта каждого разработчика. Даже при правильном использовании нулевые указатели семантически неполны, поскольку они не дают возможности выразить разницу между значением «Неприменимо» и значением «Неизвестно» или значением «Будущее».
Поскольку нулевой указатель не указывает на значимый объект, попытка доступа к данным, хранящимся в этой (недопустимой) ячейке памяти, может вызвать ошибку во время выполнения или немедленный сбой программы. Это ошибка нулевого указателя . Это один из наиболее распространенных типов недостатков программного обеспечения. [1] а Тони Хоар , представивший эту концепцию, назвал ее «ошибкой на миллиард долларов».
С [ править ]
В C два нулевых указателя любого типа гарантированно сравниваются равными. [2] Макрос препроцессора NULL
определяется как определяемая реализацией константа нулевого указателя в <stdlib.h>
, [3] что в C99 можно портативно выразить как ((void *)0)
, целое значение 0
преобразован в тип void*
(см. указатель на тип void ). [4] Стандарт C не говорит, что нулевой указатель — это то же самое, что указатель на адрес памяти 0, хотя на практике это может быть так. Разыменование нулевого указателя — неопределенное поведение в C, [5] и соответствующая реализация может предполагать, что любой разыменованный указатель не является нулевым.
На практике разыменование нулевого указателя может привести к попытке чтения или записи из памяти , которая не отображается, что приведет к ошибке сегментации или нарушению доступа к памяти. Это может проявиться в виде сбоя программы или трансформироваться в программное исключение , которое может быть перехвачено программным кодом. Однако существуют определенные обстоятельства, когда это не так. Например, в x86 реальном режиме адрес 0000:0000
доступен для чтения, а также обычно доступен для записи, и разыменование указателя на этот адрес является совершенно допустимым, но обычно нежелательным действием, которое может привести к неопределенному, но не приводящему к сбою поведению в приложении. Бывают случаи, когда разыменование указателя на нулевой адрес является намеренным и четко определенным; например, код BIOS , написанный на C для 16-разрядных устройств x86 реального режима, может записывать таблицу дескрипторов прерываний (IDT) по физическому адресу 0 машины путем разыменования нулевого указателя для записи. Компилятор также может оптимизировать разыменование нулевого указателя, избегая ошибки сегментации, но вызывая другое нежелательное поведение. [6]
С++ [ править ]
В C++, в то время как NULL
макрос был унаследован от C, целочисленный литерал нуля традиционно предпочитался для представления константы нулевого указателя. [7] Однако в C++11 появилась явная константа нулевого указателя. nullptr
и введите nullptr_t
для использования вместо этого.
Другие языки [ править ]
В некоторых средах языков программирования (например, в одной собственной реализации Lisp) [ нужна ссылка ] значение, используемое в качестве нулевого указателя (называемое nil
в Lisp ) на самом деле может быть указателем на блок внутренних данных, полезных для реализации (но не доступных явно из пользовательских программ), что позволяет использовать тот же регистр в качестве полезной константы и быстрого способа доступа к внутренним компонентам реализации. Это известно как nil
вектор.
В языках с тегированной архитектурой возможный нулевой указатель может быть заменен тегированным объединением , которое обеспечивает явную обработку исключительного случая; на самом деле, возможно, нулевой указатель можно рассматривать как помеченный указатель с вычисленным тегом.
Языки программирования используют разные литералы для нулевого указателя . В Python, например, нулевое значение называется None
. В Паскале и Swift нулевой указатель называется nil
. На Эйфеле это называется void
ссылка.
Нулевое разыменование [ править ]
Поскольку нулевой указатель не указывает на значимый объект, попытка разыменования (т. е. доступа к данным, хранящимся в этом месте памяти) с нулевым указателем обычно (но не всегда) приводит к ошибке во время выполнения или немедленному сбою программы. MITRE называет ошибку нулевого указателя одной из наиболее часто используемых уязвимостей программного обеспечения. [8]
- В C разыменование нулевого указателя является неопределённым поведением . [5] Многие реализации приводят к тому, что такой код приводит к остановке программы с нарушением прав доступа , поскольку представление нулевого указателя выбирается в качестве адреса, который никогда не выделяется системой для хранения объектов. Однако такое поведение не является универсальным. Это также не гарантируется, поскольку компиляторам разрешено оптимизировать программы при условии, что они свободны от неопределенного поведения.
- В Delphi и многих других реализациях Pascal константа
nil
представляет нулевой указатель на первый адрес в памяти, который также используется для инициализации управляемых переменных. Разыменование вызывает исключение внешней ОС, которое отображается на Pascal.EAccessViolation
экземпляр исключения, еслиSystem.SysUtils
блок связан вuses
пункт. - В Java доступ к нулевой ссылке вызывает
NullPointerException
(NPE), которые могут быть перехвачены кодом обработки ошибок, но предпочтительной практикой является обеспечение того, чтобы такие исключения никогда не возникали. - В Лиспе
nil
является объектом первого класса . По соглашению,(first nil)
являетсяnil
, как есть(rest nil)
. Итак, разыменованиеnil
в этих контекстах не приведет к ошибке, но плохо написанный код может попасть в бесконечный цикл. - В .NET доступ к нулевой ссылке приводит к
NullReferenceException
быть брошенным. Хотя их перехват обычно считается плохой практикой, этот тип исключения может быть перехвачен и обработан программой. - В Objective-C сообщения могут отправляться на
nil
объект (который является нулевым указателем), не вызывая прерывания программы; сообщение просто игнорируется, а возвращаемое значение (если есть)nil
или0
, в зависимости от типа. [9] - До появления функции предотвращения доступа в режиме супервизора (SMAP) ошибка разыменования нулевого указателя могла быть использована путем сопоставления нулевой страницы злоумышленника с адресным пространством и, следовательно, для того, чтобы нулевой указатель указывал на этот регион. это может привести к выполнению кода . В некоторых случаях [10]
Смягчение [ править ]
Существуют методы, облегчающие отладку разыменования нулевого указателя. [11] Бонд и др. [11] предложите изменить виртуальную машину Java (JVM), чтобы отслеживать распространение нуля.
Чисто функциональные языки и пользовательский код, выполняемый на многих интерпретируемых языках или языках виртуальных машин, не страдают от проблемы разыменования нулевого указателя, поскольку к указателям не предоставляется прямой доступ, а в случае чисто функциональных языков весь код и данные неизменяемы.
Если язык предоставляет или использует указатели, которые в противном случае могли бы стать недействительными, можно смягчить или избежать нулевых разыменований во время выполнения, обеспечив проверку во время компиляции с помощью статического анализа или других методов, с растущим движением в сторону синтаксической помощи со стороны таких функций языка, как те, что встречаются в современных версиях языка программирования Eiffel , [12] Д , [13] и Руст . [14]
Подобный анализ можно выполнить с помощью внешних инструментов на некоторых языках.
Альтернативы нулевым указателям [ править ]
Как правило, для каждого типа структуры или класса определите несколько объектов, представляющих некоторое состояние бизнес-логики, заменяя неопределенное поведение нулем. Например, «будущее» для обозначения поля внутри структуры, которое сейчас не будет доступно (но для которого заранее известно, что в будущем оно будет определено), «неприменимо» для обозначения поля в ненормализованном виде. структура, «ошибка», «тайм-аут», чтобы указать, что поле не может быть инициализировано (вероятно, останавливая нормальное выполнение всей программы, потока, запроса или команды).
История [ править ]
В 2009 году Тони Хоар заявил [15] что он изобрел нулевую ссылку в 1965 году как часть ALGOL W. языка В статье 2009 года Хоар описывает свое изобретение как «ошибку на миллиард долларов»:
Я называю это своей ошибкой на миллиард долларов. Это было изобретение нулевой ссылки в 1965 году. В то время я разрабатывал первую комплексную систему типов для ссылок на объектно-ориентированном языке (ALGOL W). Моя цель состояла в том, чтобы гарантировать, что любое использование ссылок должно быть абсолютно безопасным, с автоматической проверкой, выполняемой компилятором. Но я не смог устоять перед искушением добавить нулевую ссылку просто потому, что это было так легко реализовать. Это привело к бесчисленным ошибкам, уязвимостям и сбоям в системе, которые, вероятно, причинили боль и ущерб на миллиард долларов за последние сорок лет.
См. также [ править ]
Примечания [ править ]
- ^ «CWE-476: Разыменование нулевого указателя» . МИТРА .
- ^ ISO/IEC 9899 , пункт 6.3.2.3, параграф 4.
- ^ ISO/IEC 9899 , пункт 7.17, параграф 3: NULL... который расширяется до определяемой реализацией константы нулевого указателя...
- ^ ISO/IEC 9899 , пункт 6.3.2.3, параграф 3.
- ^ Jump up to: Перейти обратно: а б ISO/IEC 9899 , пункт 6.5.3.2, параграф 4, особенно. сноска 87.
- ^ Латтнер, Крис (13 мая 2011 г.). «Что каждый программист на C должен знать о неопределенном поведении № 1/3» . blog.llvm.org . Архивировано из оригинала 14 июня 2023 г. Проверено 14 июня 2023 г.
- ^ Страуструп, Бьярне (март 2001 г.). «Глава 5:
Theconst
квалификатор (§5.4) предотвращает случайное переопределениеNULL
и гарантирует, чтоNULL
может использоваться там, где требуется константа». Язык программирования C++ (14-е издание 3-го изд.). США и Канада: Аддисон – Уэсли. стр 88. . ISBN 0-201-88954-4 . - ^ «CWE-476: Разыменование нулевого указателя» . МИТРА .
- ^ Язык программирования Objective-C 2.0 , раздел «Отправка сообщений в ноль» .
- ^ «Разыменование нулевого указателя ядра OS X в AppleGraphicsDeviceControl»
- ^ Jump up to: Перейти обратно: а б Бонд, Майкл Д.; Нетеркот, Николас; Кент, Стивен В.; Гайер, Сэмюэл З.; МакКинли, Кэтрин С. (2007). «Отслеживание плохих яблок». Материалы 22-й ежегодной конференции ACM SIGPLAN по системам и приложениям объектно-ориентированного программирования — OOPSLA '07 . п. 405. дои : 10.1145/1297027.1297057 . ISBN 9781595937865 . S2CID 2832749 .
- ^ «Безопасность в пустоте: предпосылки, определение и инструменты» . Проверено 24 ноября 2021 г.
- ^ Бартош Милевский. «Язык программирования SafeD-D» . Проверено 17 июля 2014 г.
- ^ «Бесстрашная безопасность: безопасность памяти» . Архивировано из оригинала 8 ноября 2020 года . Проверено 4 ноября 2020 г. .
- ^ Тони Хоар (25 августа 2009 г.). «Нулевые ссылки: ошибка на миллиард долларов» . InfoQ.com.
Ссылки [ править ]
- Объединенный технический комитет ISO/IEC JTC 1, Подкомитет SC 22, Рабочая группа WG 14 (08 сентября 2007 г.). Международный стандарт ISO/IEC 9899 (PDF) (проект комитета).
{{cite book}}
: CS1 maint: несколько имен: список авторов ( ссылка ) CS1 maint: числовые имена: список авторов ( ссылка )