NULL (Си)
NULL в языках программирования Си и C++ — макрос, объявленный в заголовочном файле stddef.h (и других заголовочных файлах). Значением этого макроса является зависящая от реализации константа нулевого указателя (англ. null pointer constant ). Константа нулевого указателя — это целочисленное константное выражение со значением 0, или (только в Си) такое же выражение, но приведённое к типу void * . Константа нулевого указателя, приведённая к любому типу указателей, является нулевым указателем. Гарантируется, что нулевой указатель не равен указателю на любой объект (в широком смысле слова, любые данные) или функцию. Гарантируется, что любые два нулевых указателя равны между собой. Разыменовывание нулевого указателя является операцией с неопределённым поведением.
Иначе говоря, реализация предоставляет специальное значение — константу нулевого указателя, которую можно присвоить любому указателю и такой указатель при сравнении не будет равен любому «корректному» указателю. То есть, можно считать, что нулевой указатель не содержит корректный адрес в памяти.
Содержание
Использование
Нулевые указатели придуманы как удобный способ «отметить» указатели, которые заведомо не указывают на корректный адрес в памяти. Например, при объявлении указателя как автоматической переменной его значение не определено. Чтобы отметить, что этот указатель ещё не содержит корректный адрес в памяти, такому указателю присваивают константу нулевого указателя:
Хорошим стилем программирования является присваивание указателю после освобождения памяти, на которую он ссылался, нулевого указателя. Кроме этого, применение обнуления указателей актуально для безопасности освобождения памяти: операция delete в C++ (free в Си) безопасна для нулевого указателя. Например:
в то время как в таком варианте ошибки не будет
Разыменовывание нулевых указателей
Разыменовывание нулевого указателя является операцией с неопределённым поведением. На реализацию не накладывается никаких ограничений: может произойти, например, обращение к памяти, не предназначенной для использования данной программой (то есть при чтении будет считан «мусор», а при записи — значение будет записано в область памяти, не принадлежащую программе). Например, в DOS запись по нулевому адресу затрёт как минимум нулевой вектор прерываний, так что следующий вызов int 0 приведёт, скорее всего, к зависанию системы. Однако чаще всего это приводит к ошибке времени выполнения (если в операционной системе реализована защита памяти и доступ в невыделенную процессу память блокируется). Например, в Windows 9x сообщение «Общая ошибка защиты» — «Программа выполнила недопустимую операцию и будет закрыта» (англ. general protection fault, GPF ) выдаётся чаще всего в тех случаях, когда программа обращается в память по некорректному (в том числе неинициализированному или уже освобождённому) указателю. В Unix-подобных операционных системах в таких ситуациях процесс получает сигнал SIGSEGV и его обработчик выводит сообщение «Segmentation fault».
Нулевые указатели в C++
В отличие от классического Си в C++ значение пустого указателя предопределено стандартом языка и всегда равно 0 (целочисленному нулю, приведённому к типу «указатель»). Поэтому в программах на C++ не только возможно, но и рекомендуется использовать значение 0 вместо NULL [1] , однако некоторые программисты считают, что это ухудшает читаемость исходного кода. В стандарте C++11 для обозначения нулевого указателя добавлено новое ключевое слово nullptr [2] [3] .
См. также
Примечания
- ↑Страуструп Б. 5.1.1 «Ноль» // Язык программирования C++. Специальное издание = The C++ programming language. Special edition. — М .: Бином-Пресс, 2007. — 1104 с. — ISBN 5-7989-0223-4
- ↑JTC1/SC22/WG21 — The C++ Standards CommitteeSC22/WG21/N2431 = J16/07-0301 «A name for the null pointer: nullptr» (англ.) (PDF). JTC1.22.32. The C++ Standards Committee (2 October 2007). Архивировано из первоисточника 11 февраля 2012.Проверено 4 октября 2010. (англ.)
- ↑Scott Meyers, Summary of C++11 Feature Availability in gcc and MSVC, 16 August 2011
Ссылки
-
(англ.) (англ.)
- C++
- Язык программирования Си
Wikimedia Foundation . 2010 .
Полезное
Смотреть что такое «NULL (Си)» в других словарях:
Null — (de) … Kölsch Dialekt Lexikon
null — null … Hochdeutsch — Plautdietsch Wörterbuch
Null — Pour le musicien japonais, voir Kazuyuki K. Null. NULL est un mot clef présent dans de nombreux langages informatiques, et qui désigne l état d un pointeur qui n a pas de cible ou d une variable qui n a pas de valeur. La notion de valeur ou … Wikipédia en Français
Null — may refer to: Contents 1 In computing 2 In art 3 In mathematics 4 In science 5 People … Wikipedia
Null — «Null» redirige aquí. Para otras acepciones, véase Null (desambiguación). El término null o nulo es a menudo utilizado en la computación, haciendo referencia a la nada. En programación, null resulta ser un valor especial aplicado a un puntero (o… … Wikipedia Español
null — [nʌl] adjective [only before a noun] 1. STATISTICS a null effect, result etc is one that is zero or nothing 2. LAW another name for null and void: • Their suit also asks the court to declare null the buyer s shareholder rights plan. * * * … Financial and business terms
NULL (Си и Си++) — NULL в языках программирования Си и C++ макрос, объявленный в заголовочном файле stddef.h (и других заголовочных файлах). Значением этого макроса является зависящая от реализации константа нулевого указателя (англ. null pointer constant).… … Википедия
null — / nəl/ adj [Anglo French nul, literally, not any, from Latin nullus, from ne not + ullus any]: having no legal or binding force: void a null contract Merriam Webster’s Dictionary of Law. Merriam Webster. 1996 … Law dictionary
Null — Null, a. [L. nullus not any, none; ne not + ullus any, a dim. of unus one; cf. F. nul. See
Null-O — is a 1958 science fiction short story by Philip K. Dick. This rather brief story examines the concept of totally unempathic and logical humans ( Null O s) in an obvious parody of the plot and concepts of The Players of Null A by A. E. van Vogt.… … Wikipedia
Null — Sf std. (16. Jh.) Entlehnung. Entlehnt aus l. nulla gleicher Bedeutung, feminine Substantivierung von l. nullus keiner . Dieses ist eine Lehnbedeutung von arab. ṣifr, das ebenfalls Null und leer bedeutet und das seinerseits ai. śūnya Null, leer… … Etymologisches Wörterbuch der deutschen sprache
Указатели
Э то, пожалуй, самая сложная и самая важная тема во всём курсе. Без понимания указателей дальнейшее изучении си будет бессмысленным. Указатели – очень простая концепция, очень логичная, но требующая внимания к деталям.
Определение
У казатель – это переменная, которая хранит адрес области памяти. Указатель, как и переменная, имеет тип. Синтаксис объявления указателей
Например
float *a;
long long *b;
Два основных оператора для работы с указателями – это оператор & взятия адреса, и оператор * разыменования. Рассмотрим простой пример.
Рассмотрим код внимательно, ещё раз
Была объявлена переменная с именем A. Она располагается по какому-то адресу в памяти. По этому адресу хранится значение 100.
Создали указатель типа int.
Теперь переменная p хранит адрес переменной A. Используя оператор * мы получаем доступ до содержимого переменной A.
Чтобы изменить содержимое, пишем
После этого значение A также изменено, так как она указывает на ту же область памяти. Ничего сложного.
Теперь другой важный пример
Будет выведено
4
4
8
4
Несмотря на то, что переменные имеют разный тип и размер, указатели на них имеют один размер. Действительно, если указатели хранят адреса, то они должны быть целочисленного типа. Так и есть, указатель сам по себе хранится в переменной типа size_t (а также ptrdiff_t), это тип, который ведёт себя как целочисленный, однако его размер зависит от разрядности системы. В большинстве случаев разницы между ними нет. Зачем тогда указателю нужен тип?
Арифметика указателей
В о-первых, указателю нужен тип для того, чтобы корректно работала операция разыменования (получения содержимого по адресу). Если указатель хранит адрес переменной, необходимо знать, сколько байт нужно взять, начиная от этого адреса, чтобы получить всю переменную.
Во-вторых, указатели поддерживают арифметические операции. Для их выполнения необходимо знать размер.
операция + N сдвигает указатель вперёд на N*sizeof(тип) байт.
Например, если указатель int *p; хранит адрес CC02, то после p += 10; он будет хранить адрес СС02 + sizeof(int)*10 = CC02 + 28 = CC2A (Все операции выполняются в шестнадцатиричном формате). Пусть мы создали указатель на начало массива. После этого мы можем «двигаться» по этому массиву, получая доступ до отдельных элементов.
Заметьте, каким образом мы получили адрес первого элемента массива
Массив, по сути, сам является указателем, поэтому не нужно использовать оператор &. Мы можем переписать пример по-другому
Получить адрес первого элемента и относительно него двигаться по массиву.
Кроме операторов + и — указатели поддерживают операции сравнения. Если у нас есть два указателя a и b, то a > b, если адрес, который хранит a, больше адреса, который хранит b.
Если же указатели равны, то они указывают на одну и ту же область памяти.
Указатель на указатель
У казатель хранит адрес области памяти. Можно создать указатель на указатель, тогда он будет хранить адрес указателя и сможет обращаться к его содержимому. Указатель на указатель определяется как
Очевидно, ничто не мешает создать и указатель на указатель на указатель, и указатель на указатель на указатель на указатель и так далее. Это нам понадобится при работе с двумерными и многомерными массивами. А вот простой пример, как можно работать с указателем на указатель.
Указатели и приведение типов
Т ак как указатель хранит адрес, можно кастовать его до другого типа. Это может понадобиться, например, если мы хотим взять часть переменной, или если мы знаем, что переменная хранит нужный нам тип.
В этом примере мы пользуемся тем, что размер типа int равен 4 байта, а char 1 байт. За счёт этого, получив адрес первого байта, можно пройти по остальным байтам числа и вывести их содержимое.
NULL pointer — нулевой указатель
У казатель до инициализации хранит мусор, как и любая другая переменная. Но в то же время, этот «мусор» вполне может оказаться валидным адресом. Пусть, к примеру, у нас есть указатель. Каким образом узнать, инициализирован он или нет? В общем случае никак. Для решения этой проблемы был введён макрос NULL библиотеки stdlib.
Принято при определении указателя, если он не инициализируется конкретным значением, делать его равным NULL.
По стандарту гарантировано, что в этом случае указатель равен NULL, и равен нулю, и может быть использован как булево значение false. Хотя в зависимости от реализации NULL может и не быть равным 0 (в смысле, не равен нулю в побитовом представлении, как например, int или float).
Это значит, что в данном случае
вполне корректная операция, а в случае
поведение не определено. То есть указатель можно сравнивать с нулём, или с NULL, но нельзя NULL сравнивать с переменной целого типа или типа с плавающей точкой.
Примеры
Теперь несколько примеров работы с указателями
1. Пройдём по массиву и найдём все чётные элементы.
2. Когда мы сортируем элементы часто приходится их перемещать. Если объект занимает много места, то операция обмена местами двух элементов будет дорогостоящей. Вместо этого можно создать массив указателей на исходные элементы и отсортировать его. Так как размер указателей меньше, чем размер элементов целевого массива, то и сортировка будет происходить быстрее. Кроме того, массив не будет изменён, часто это важно.
3. Более интересный пример. Так как размер типа char всегда равен 1 байт, то с его помощью можно реализовать операцию swap – обмена местами содержимого двух переменных.
В этом примере можно поменять тип переменных a и b на double или любой другой (с соответствующим изменением вывода и вызова sizeof), всё равно мы будет обменивать местами байты двух переменных.
Что такое NULL и для чего он нужен
Когда я только начинал изучать программирование на языке C на первых курсах вуза, слово NULL , которое я однажды увидел в коде, приведённом лектором на доске, привело меня пусть и не в благоговейный, но, всё-таки, в трепет. Слово, вроде, похоже на «ноль», но на самом деле, наверно, не ноль. Для чего нужен этот вызывающе-угрожающий «капслок»? А имеет ли этот NULL какое-нибудь отношение к нулю? На эти вопросы, возникающие в моей голове, ответов не находилось.
А перед этим словом шло странное двойное равенство. Зачем же преподаватель дважды написал знак «равно»? Сначала я подумал, что, это, наверно, описка. Или же мел сплоховал в середине длинных чёрточек. Но в той же самой конструкции, которая содержала NULL и двойное равенство, было ещё и обычное равенство, и чёрточки в нём были совсем не длинные. И я начал смутно догадываться о том, что NULL — это какой-то необычный ноль, а два знака «равно» означают какое-то необычное равенство. Всё в этом новом и непонятном для меня языке программирования было необычно.
Эту строку, которая повергла меня в шок много лет назад, я приведу в данной статье. А сейчас мы попробуем разобраться с тем, что представляет собой NULL , как он используется и можно ли заменить его «обычным» нулём.
NULL — это макрос, определение которого содержится в библиотеке stdlib.h . Его значением является адрес, доступ к которому не предусмотрен. Программа, в которой используется макрос NULL , никогда не размещает никаких данных по этому адресу.
Значение NULL может быть присвоено указателю любого типа, в том числе, и нетипизированному. Например, все следующие присвоения корректны:
Указатель, содержащий значение NULL , называется нулевым. Таким образом, указатели p1 , p2 и p3 , описанные выше, – нулевые.
Давайте теперь рассмотрим ситуации, в которых используются макрос NULL и нулевые указатели.
Функции динамического выделения памяти обычно обычно возвращают адрес выделенного участка в случае успеха, а в случае неудачи возвращают NULL . Именно таким образом ведёт себя, например, библиотечная функция malloc() . Благодаря этому, код, вызывающий такую функцию, может корректно обработать ситуацию, в которой память выделить не удалось; проиллюстрируем это следующим примером:
2-я строка в этом примере — это как раз та, которую я упоминал в начале статьи.
В более общем случае, функции, генерирующие некоторые данные, динамически выделяющие под них память, заполняющие её этими данными и возвращающие адрес выделенного участка, могут возвращать NULL и как признак того, что данные сгенерировать не удалось, например, вследствие ошибки в переданных функции параметрах.
Значение NULL обычно присваивают указателю для того, чтобы отличать его от указателя, содержащего адрес участка памяти, к которому можно обращаться с целью чтения из него информации или записи информации в него. Например, при создании указателя можно сразу присвоить ему значение NULL . В дальнейшем, код может проверить значение этого указателя и сразу же «понять», можно ли обращаться по содержащемуся в нём адресу или нет. Рассмотрим простой пример:
Другой пример: указателю был присвоен адрес динамически выделенного участка памяти. Через некоторое время выделенная память была утилизирована. Теперь обращение по адресу, хранящемуся в указателе, может привести к непредсказуемым последствиям. Чтобы избежать этого, указателю присваивают значение NULL , как индикатор того, что разыменовывать указатель нельзя.
Кстати, библиотечная функция free() , занимающаяся освобождением динамически выделенной памяти и принимающая в качестве аргумента адрес выделенного участка, корректно обрабатывает значение NULL . Таким образом, если передать ей нулевой указатель, то ничего страшного не произойдёт. Между прочим, не произойдёт вообще ничего!
В односвязном списке каждый узел, за исключением последнего, связан с последующим. В программной реализации списка узлы обычно представляются переменными структурного типа (для простоты будем эти переменные тоже называть узлами), а упомянутая связь между узлами реализуется следующим образом. Каждый узел содержит в качестве поля указатель на узел, следующий за ним. Исключением является последний узел списка, соответствующему указателю которого указывать не на что. Представляется вполне разумным присвоить ему значение NULL .
Этот нулевой указатель будет являться признаком того, что узел, в котором он содержится, — последний в списке. Данным признаком очень удобно пользоваться во время перебора всех узлов списка в цикле: мы переходим от узла к узлу, пока не «наткнёмся» на нулевой указатель. На этом цикл заканчивается.
Пусть, например, тип узла описывается следующим образом:
Тогда функция, печатающая на отдельной строке содержимое каждого узла, может выглядеть так:
Как только указатель на следующий узел окажется равным NULL , итерации прекращаются.
Как мы видим, NULL можно использовать в программе везде, где можно использовать адреса. NULL можно присваивать указателям, возвращать из функций, передавать в функции. Правда, в последнем случае нужно быть уверенным, что функция «умеет» корректно обрабатывать NULL . Например, функция free() таким умением обладает, а приведённая выше функция print() — нет.
А вот что нельзя делать — так это разыменовывать нулевые указатели! Поведение программы в результате выполнения этой операции непредсказуемо. В любом случае, ничего хорошего ожидать не приходится. Эксперименты показывают, что при использовании компилятора MinGW64 в среде Windows 7 операционная система сразу же «убивает» процесс, попытавшийся обратиться по «запрещённому» адресу.
Зададимся вопросом: а можно ли вообще обходиться без макроса NULL , используя вместо него константу, являющуюся его значением? Разумеется, можно, но только для этого нужно её знать. Один из самых простых способов узнать эту константу — вывести NULL на печать, например, следующим образом:
Здесь мы применили спецификатор формата %p , предназначенный для отображения адресов. В случае использования компилятора MinGW64 результат выполнения данной строки будет следующим:
Ну что ж, оказалось, что, по крайней мере, в случае MinGW64, NULL — это ноль!
Другой способ установления значения макроса NULL заключается в нахождении его определения в файле stdlib.h (при условии, что этот файл доступен). Снова обратимся к компилятору MinGW64. В файле stdlib.h , входящем в состав этого компилятора, имеются следующие макроопределения (для удобства восприятия я расставил отступы в началах строк; в оригинале они отсутствуют):
Поскольку при использовании языка C99 макрос __cplusplus не определён, актуальным для нас является определение макроса NULL , данное в 9-й строке. Как мы видим, NULL определяется как ноль, приведённый к типу void * .
Логично задаться вопросом: а для чего вообще нужен макрос NULL ? Не проще ли везде и всегда вместо него использовать просто ноль? Ответ: всё дело в переносимости кода. Гипотетически можно допустить существование таких компиляторов, в которых NULL имеет значение, отличное от ноля (хотя я таких компиляторов не встречал). Использование макроса NULL позволит разным компиляторам во время компиляции одного и того же кода заменять этот макрос тем его значением, который соответствует данному компилятору. Если же использовать вместо NULL ноль, то в тех компиляторах, в которых NULL != 0 , скомпилированный код может оказаться неработоспособным, а в худшем случае его выполнение может привести к повреждению расположенных в памяти данных, не имеющих отношения к выполняемой программе.
Лично меня переносимость моего кода на языке C99 совершенно не волнует. Меня заботит лишь его совместимость с тем компилятором, который я использую, а именно — MinGW64. Что касается NULL , то его применение я нахожу неудобным. Куда приятнее, например, писать » if (p) «, чем » if (p != NULL) «. Поэтому я никогда не использую NULL , обходясь вместо него нулём. Причём, я даже не привожу ноль к типу void * , поскольку такое приведение осуществляется компилятором автоматически.
Надеюсь, что читатель вынес для себя что-нибудь полезное из этой статьи и решил, стоит ему использовать макрос NULL , или нет.