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