В вычислительной технике нулевой указатель или нулевая ссылка — это значение, сохраненное для указания того, что указатель или ссылка не ссылается на допустимый объект . Программы обычно используют нулевые указатели для представления таких условий, как конец списка неизвестной длины или невозможность выполнить какое-либо действие; такое использование нулевых указателей можно сравнить с типами, допускающими значение null , и со значением Nothing в опциональном типе .
Не следует путать нулевой указатель с неинициализированным указателем : нулевой указатель гарантированно не равен любому указателю, указывающему на допустимый объект. Однако, в общем, большинство языков не предоставляют такой гарантии для неинициализированных указателей. Он может быть равен другим допустимым указателям или равен нулевым указателям. Он может делать и то, и другое в разное время, или поведение сравнения может быть неопределенным . Кроме того, в языках, предлагающих такую поддержку, правильное использование зависит от индивидуального опыта каждого разработчика и инструментов линтера. Даже при правильном использовании нулевые указатели семантически неполны , поскольку они не дают возможности выразить разницу между значением «Неприменимо» и значением «Неизвестно» или значением «Будущее».
Поскольку нулевой указатель не указывает на значимый объект, попытка доступа к данным, хранящимся в этой (недопустимой) ячейке памяти, может вызвать ошибку времени выполнения или немедленный сбой программы. Это ошибка нулевого указателя . Это один из наиболее распространенных типов уязвимостей программного обеспечения, [1] и Тони Хоар , который представил эту концепцию, назвал ее «ошибкой на миллиард долларов». [2]
В языке C два нулевых указателя любого типа гарантированно считаются равными при сравнении. [3] Макрос препроцессора NULL
определен как константа нулевого указателя, определяемая реализацией в , [4] который в C99 может быть переносимо выражен как , целочисленное значение, преобразованное в тип (см. указатель на тип void ). [5] Стандарт C не говорит, что нулевой указатель совпадает с указателем на адрес памяти 0, хотя на практике это может иметь место. Разыменование нулевого указателя является неопределенным поведением в C, [6] и соответствующая реализация может предполагать, что любой разыменованный указатель не является нулевым.<stdlib.h>
((void *)0)
0
void*
На практике разыменование нулевого указателя может привести к попытке чтения или записи из памяти , которая не отображена, вызывая ошибку сегментации или нарушение доступа к памяти. Это может проявиться как сбой программы или трансформироваться в программное исключение , которое может быть перехвачено программным кодом. Однако существуют определенные обстоятельства, когда это не так. Например, в реальном режиме x86 адрес доступен для чтения и также обычно доступен для записи, и разыменование указателя на этот адрес является совершенно допустимым, но обычно нежелательным действием, которое может привести к неопределенному, но не вызывающему сбоя поведению в приложении; если нулевой указатель представлен как указатель на этот адрес, его разыменование приведет к такому поведению. Бывают случаи, когда разыменование указателя на нулевой адрес является преднамеренным и четко определенным; например, код BIOS , написанный на языке C для 16-битных устройств реального режима x86, может записывать таблицу дескрипторов прерываний (IDT) по физическому адресу 0 машины, разыменовывая указатель с тем же значением, что и нулевой указатель для записи. Компилятор также может оптимизировать разыменование нулевого указателя, избегая ошибки сегментации, но вызывая другое нежелательное поведение. [7]0000:0000
В C++, хотя NULL
макрос был унаследован от C, целочисленный литерал для нуля традиционно предпочитался для представления константы нулевого указателя. [8] Однако в C++11 была введена явная константа нулевого указателя nullptr
и тип, nullptr_t
которые следует использовать вместо нее.
В некоторых средах языка программирования (например, по крайней мере в одной фирменной реализации Lisp) [ требуется ссылка ] значение, используемое в качестве нулевого указателя (называемого nil
в Lisp ), на самом деле может быть указателем на блок внутренних данных, полезных для реализации (но явно не доступных из пользовательских программ), что позволяет использовать тот же регистр в качестве полезной константы и быстрого способа доступа к внутренним данным реализации. Это известно как вектор nil
.
В языках с теговой архитектурой возможно нулевой указатель можно заменить теговым объединением , которое обеспечивает явную обработку исключительного случая; фактически, возможно нулевой указатель можно рассматривать как теговый указатель с вычисляемым тегом.
В языках программирования используются различные литералы для нулевого указателя . Например, в Python нулевое значение называется None
. В Pascal и Swift нулевой указатель называется nil
. В Eiffel он называется void
ссылкой.
Поскольку нулевой указатель не указывает на значимый объект, попытка разыменовать (т. е. получить доступ к данным, хранящимся в этой ячейке памяти) нулевой указатель обычно (но не всегда) вызывает ошибку времени выполнения или немедленный сбой программы. MITRE перечисляет ошибку нулевого указателя как одну из наиболее часто используемых уязвимостей программного обеспечения. [9]
nil
представляет собой нулевой указатель на первый адрес в памяти, который также используется для инициализации управляемых переменных. Разыменование вызывает внешнее исключение ОС, которое отображается на EAccessViolation
экземпляр исключения Pascal, если System.SysUtils
блок связан в uses
предложении.NullPointerException
(NPE), которое может быть обнаружено кодом обработки ошибок, но предпочтительная практика — гарантировать, что такие исключения никогда не возникнут.nil
это объект первого класса . По соглашению (first nil)
is nil
, как и is (rest nil)
. Поэтому разыменование nil
в этих контекстах не вызовет ошибку, но плохо написанный код может попасть в бесконечный цикл.NullReferenceException
к выбросу. Хотя перехват таких исключений обычно считается плохой практикой, этот тип исключения может быть перехвачен и обработан программой.nil
(который является нулевым указателем), не вызывая прерывания программы; сообщение просто игнорируется, а возвращаемое значение (если есть) равно nil
или 0
, в зависимости от типа. [10]Хотя у нас могут быть языки без нулевых значений, в большинстве из них вероятность нулевых значений есть, поэтому существуют методы, позволяющие избежать или облегчить отладку разыменования нулевых указателей. [12] Бонд и др. [12] предлагают модифицировать виртуальную машину Java (JVM) для отслеживания распространения нулевых значений.
У нас есть три уровня обработки нулевых ссылок в порядке эффективности:
1. языки без нуля;
2. языки, которые могут статически анализировать код, чтобы избежать возможности разыменования null во время выполнения;
3. если во время выполнения может произойти разыменование null, инструменты, помогающие отладить.
Чистые функциональные языки являются примером уровня 1, поскольку прямой доступ к указателям не предоставляется, а весь код и данные являются неизменяемыми. Пользовательский код, работающий на интерпретируемых или виртуальных машинных языках, обычно не страдает от проблемы разыменования нулевого указателя. [ dubious – discussion ]
В тех случаях, когда язык предоставляет или использует указатели, которые могут стать недействительными, можно избежать разыменования null во время выполнения, обеспечив проверку во время компиляции с помощью статического анализа или других методов, с синтаксической помощью языковых функций, таких как те, которые можно увидеть в языке программирования Eiffel с безопасностью Void [13], чтобы избежать разыменования null, D [ 14] и Rust [15] .
В некоторых языках анализ можно выполнять с помощью внешних инструментов, но они слабы по сравнению с прямой поддержкой языка с проверками компилятора, поскольку они ограничены самим определением языка.
Последним средством уровня 3 является случай, когда во время выполнения возникает нулевая ссылка; в этом случае могут помочь средства отладки.
Как правило, для каждого типа структуры или класса определяются некоторые объекты, представляющие некоторое состояние бизнес-логики, заменяющие неопределенное поведение на null. Например, «future» для указания поля внутри структуры, которое не будет доступно прямо сейчас (но для которого мы заранее знаем, что в будущем оно будет определено), «not applied» для указания поля в ненормализованной структуре, «error», «timeout» для указания того, что поле не может быть инициализировано (вероятно, остановив нормальное выполнение полной программы, потока, запроса или команды).
В 2009 году Тони Хоар заявил [16] [17] , что он изобрел нулевую ссылку в 1965 году как часть языка ALGOL W. В этой ссылке 2009 года Хоар описывает свое изобретение как «ошибку на миллиард долларов»:
Я называю это своей ошибкой на миллиард долларов. Это было изобретение нулевой ссылки в 1965 году. В то время я проектировал первую всеобъемлющую систему типов для ссылок в объектно-ориентированном языке (ALGOL W). Моей целью было гарантировать, что все использование ссылок должно быть абсолютно безопасным, с автоматической проверкой, выполняемой компилятором. Но я не мог устоять перед искушением ввести нулевую ссылку, просто потому, что это было так легко реализовать. Это привело к бесчисленным ошибкам, уязвимостям и сбоям системы, которые, вероятно, нанесли миллиард долларов боли и ущерба за последние сорок лет.
const
NULL
NULL
0-201-88954-4.