В C (или C ++, если на то пошло) указатели являются особенными, если они имеют нулевое значение: я советую устанавливать указатели на ноль после освобождения их памяти, потому что это означает, что освобождение указателя снова не опасно; когда я вызываю malloc, он возвращает указатель с нулевым значением, если не может получить память; Я if (p != 0)
все время использую, чтобы убедиться, что переданные указатели действительны и т. Д.
Но поскольку адресация памяти начинается с 0, не является ли 0 таким же допустимым адресом, как любой другой? Как можно использовать 0 для обработки нулевых указателей, если это так? Почему вместо этого отрицательное число не равно нулю?
Редактировать:
Куча хороших ответов. Я резюмирую сказанное в ответах, выраженных так, как это интерпретирует мой собственный разум, и надеюсь, что сообщество поправит меня, если я неправильно пойму.
Как и все остальное в программировании, это абстракция. Просто константа, не имеющая отношения к адресу 0. C ++ 0x подчеркивает это, добавляя ключевое слово
nullptr
.Это даже не абстракция адреса, это константа, указанная стандартом C, и компилятор может преобразовать ее в какое-то другое число, если он никогда не будет равняться «реальному» адресу и равен другим нулевым указателям, если 0 не является лучшее соотношение цены и качества для платформы.
В случае, если это не абстракция, как было в первые дни, адрес 0 используется системой и недоступен для программиста.
Я признаю, что мое предложение об отрицательном числе было немного сумасшедшим мозговым штурмом. Использование целого числа со знаком для адресов немного расточительно, если это означает, что помимо нулевого указателя (-1 или любого другого) пространство значений равномерно делится между положительными целыми числами, которые образуют действительные адреса, и отрицательными числами, которые просто теряются.
Если какое-либо число всегда может быть представлено типом данных, оно равно 0. (Вероятно, 1 тоже. Я думаю о однобитовом целом, которое будет 0 или 1, если без знака, или просто битом со знаком, если он подписан, или двухбитным целым числом, которое будет [-2, 1]. Но тогда вы могли бы просто указать, что 0 - это ноль, а 1 - единственный доступный байт в памяти.)
Я все еще кое-что не решила. Вопрос о переполнении стека Указатель на конкретный фиксированный адрес говорит мне, что даже если 0 для нулевого указателя является абстракцией, другие значения указателя не обязательно. Это заставляет меня задавать еще один вопрос о переполнении стека: могу ли я когда-нибудь получить доступ к нулевому адресу? ,
if (p != 0)
наif (p)
который является общим идиома в C и C ++, хотя вам придется выйти из привычки , если вы берете на Java.0xDEADBEEF
.Ответы:
2 балла:
только постоянное значение 0 в исходном коде является нулевым указателем - реализация компилятора может использовать любое значение, которое она хочет или нуждается в работающем коде. Некоторые платформы имеют специальное значение указателя, которое является «недопустимым», которое реализация может использовать в качестве нулевого указателя. В FAQ по C есть вопрос: «Серьезно, действительно ли на каких-либо машинах использовались ненулевые нулевые указатели или разные представления для указателей на разные типы?» , который указывает на несколько платформ, которые использовали это свойство 0 как нулевой указатель в источнике C, хотя во время выполнения они представлялись по-разному. В стандарте C ++ есть примечание, поясняющее, что преобразование «интегрального константного выражения со значением ноль всегда дает нулевой указатель,
отрицательное значение может быть использовано платформой так же, как и адрес - стандарт C просто должен был выбрать что-то для использования для обозначения нулевого указателя, и был выбран ноль. Честно говоря, я не уверен, учитывались ли другие дозорные ценности.
Единственные требования для нулевого указателя:
источник
Исторически сложилось так, что адресное пространство, начинающееся с 0, всегда было ПЗУ, используемым для некоторых операционных систем или подпрограмм обработки прерываний низкого уровня, в настоящее время, поскольку все является виртуальным (включая адресное пространство), операционная система может отображать любое выделение на любой адрес, поэтому она может специально НЕ размещать ничего по адресу 0.
источник
IIRC, значение «нулевого указателя» не обязательно равно нулю. Компилятор переводит 0 в любое подходящее для системы "нулевое" значение (которое на практике, вероятно, всегда равно нулю, но не обязательно). Тот же перевод применяется всякий раз, когда вы сравниваете указатель с нулем. Поскольку вы можете сравнивать указатели только друг с другом и с этим специальным значением-0, это изолирует программиста от знания чего-либо о представлении системы в памяти. Что касается того, почему они выбрали 0 вместо 42 или чего-то подобного, я собираюсь предположить, что это потому, что большинство программистов начинают считать с 0 :) (Кроме того, в большинстве систем 0 - это первый адрес памяти, и они хотели, чтобы это было удобно, поскольку в Практические переводы, которые я описываю, на самом деле происходят редко; язык просто позволяет их).
источник
int* p = 0
) может создать указатель, содержащий значение0xdeadbeef
или любое другое значение, которое он предпочитает. 0 - это нулевой указатель, но нулевой указатель не обязательно является указателем на нулевой адрес. :)Вы, должно быть, неправильно понимаете значение постоянного нуля в контексте указателя.
Ни в C, ни в C ++ указатели не могут «иметь нулевое значение». Указатели не являются арифметическими объектами. Они не могут иметь числовых значений, таких как «ноль», «отрицательное значение» или что-нибудь в этом роде. Так что ваше утверждение о том, что «указатели ... имеют нулевое значение», просто не имеет смысла.
В C и C ++ указатели могут иметь зарезервированное значение нулевого указателя . Фактическое представление значения нулевого указателя не имеет ничего общего ни с какими «нулями». Это может быть что угодно, подходящее для данной платформы. Верно, что на большинстве платформ значение нулевого указателя физически представлено фактическим значением нулевого адреса. Однако, если на какой-то платформе адрес 0 фактически используется для каких-то целей (например, вам может потребоваться создать объекты по адресу 0), значение нулевого указателя на такой платформе, скорее всего, будет другим. Он может быть физически представлен как
0xFFFFFFFF
значение адреса или как0xBAADBAAD
значение адреса.Тем не менее, независимо от того, как значение нулевого указателя представлено на данной платформе, в вашем коде вы все равно продолжите обозначать нулевые указатели константой
0
. Чтобы присвоить значение нулевого указателя данному указателю, вы продолжите использовать такие выражения, какp = 0
. Компилятор несет ответственность за то, чтобы реализовать то, что вы хотите, и преобразовать это в правильное представление значения нулевого указателя, то есть преобразовать его в код, который, например, поместит значение адреса0xFFFFFFFF
в указательp
.Короче говоря, тот факт, что вы используете
0
в своем волшебном коде для генерации значений нулевого указателя, не означает, что значение нулевого указателя каким-то образом привязано к адресу0
. То,0
что вы используете в исходном коде, - это просто «синтаксический сахар», который не имеет абсолютно никакого отношения к фактическому физическому адресу, на который «указывает» значение нулевого указателя.источник
(p1 - nullptr) - (p2 - nullptr) == (p1 - p2)
.NULL
явно не обязательно должен быть представлен нулемВ некоторых / многих / всех операционных системах адрес памяти 0 в некотором роде особенный. Например, он часто отображается в недопустимую / несуществующую память, что вызывает исключение, если вы пытаетесь получить к ней доступ.
Я думаю, что значения указателя обычно обрабатываются как числа без знака: в противном случае, например, 32-разрядный указатель мог бы адресовать только 2 ГБ памяти вместо 4 ГБ.
источник
Я предполагаю, что магическое значение 0 было выбрано для определения недопустимого указателя, поскольку его можно было проверить с меньшим количеством инструкций. Некоторые машинные языки автоматически устанавливают флаги нуля и знака в соответствии с данными при загрузке регистров, чтобы вы могли проверить нулевой указатель с помощью простых инструкций загрузки и перехода без выполнения отдельной инструкции сравнения.
(Большинство ISA устанавливают флаги только для инструкций ALU, но не загружают. И обычно вы не создаете указатели посредством вычислений, за исключением компилятора, когда анализируете исходный код C Но, по крайней мере, вам не нужна произвольная константа ширины указателя для сравнить с.)
На Commodore Pet, Vic20 и C64, которые были первыми машинами, на которых я работал, оперативная память начиналась с адреса 0, поэтому было вполне допустимо читать и писать с использованием нулевого указателя, если вы действительно этого хотели.
источник
Я думаю, это просто условность. Для обозначения недопустимого указателя должно быть какое-то значение.
Вы просто теряете один байт адресного пространства, что редко должно быть проблемой.
Нет отрицательных указателей. Указатели всегда беззнаковые. Кроме того, если они могут быть отрицательными, ваше соглашение будет означать, что вы потеряете половину адресного пространства.
источник
char *p = (char *)1; --p;
. Поскольку поведение нулевого указателя не определено стандартом, эта система можетp
фактически иметь адрес чтения и записи 0, приращение для присвоения адреса1
и т. Д.char x = ((char*)0);
чтения нулевого адреса и сохранения этого значения в x. Такой код привел бы к неопределенному поведению для любой реализации, которая не определяла его поведение, но тот факт, что в стандарте указано, что что-то является неопределенным поведением, никоим образом не запрещает реализациям предлагать свои собственные спецификации того, что он будет делать.*(char *)0
. Это правда, но в моем предложении реализация не должна определять поведение*(char *)0
или любые другие операции с нулевым указателем.char *p = (char*)1; --p;
было бы определено стандартом только в том случае, если эта последовательность была выполнена после того, как указатель на что-то, кроме первого байта объекта, был приведен к anintptr_t
, и результат этого приведения оказался равным 1 , и в этом конкретном случае результат--p
выдаст указатель на байт, предшествующий тому, значение указателя которого при приведении к немуintptr_t
было возвращено1
.Хотя C использует 0 для представления нулевого указателя, имейте в виду, что значение самого указателя может не быть нулем. Однако большинство программистов будут использовать только те системы, в которых нулевой указатель фактически равен 0.
Но почему ноль? Ну, это один адрес, который разделяет каждая система. И часто младшие адреса зарезервированы для целей операционной системы, поэтому значение работает хорошо, не ограничиваясь прикладными программами. Случайное присвоение целочисленного значения указателю с такой же вероятностью приведет к нулю, как и все остальное.
источник
Исторически небольшая часть памяти приложения была занята системными ресурсами. Именно в те дни ноль стал нулевым значением по умолчанию.
Хотя это не обязательно верно для современных систем, все же плохая идея устанавливать значения указателя на что-либо, кроме того, что вам предоставило выделение памяти.
источник
Относительно аргумента о том, что указатель не устанавливается в нуль после его удаления, чтобы в будущем удалял "обнаруживать ошибки" ...
Если вы действительно очень обеспокоены этим, то лучший подход, который гарантированно работает, - использовать assert ():
Это требует некоторого дополнительного набора текста и одной дополнительной проверки во время сборки отладки, но она обязательно даст вам то, что вы хотите: обратите внимание, когда ptr удаляется дважды. Альтернатива, приведенная в обсуждении комментария, не устанавливающая указатель на нуль, что приведет к сбою, просто не гарантирует успеха. Хуже того, в отличие от описанного выше, это может вызвать сбой (или намного хуже!) У пользователя, если одна из этих «ошибок» попадет на полку. Наконец, эта версия позволяет вам продолжать запускать программу, чтобы увидеть, что на самом деле происходит.
Я понимаю, что это не отвечает на заданный вопрос, но я был обеспокоен тем, что кто-то, читающий комментарии, может прийти к выводу, что считается `` хорошей практикой '' НЕ устанавливать указатели на 0, если возможно, они будут отправлены в free () или удалить дважды. В тех немногих случаях, когда это возможно, НИКОГДА не рекомендуется использовать Undefined Behavior в качестве инструмента отладки. Никто, кому когда-либо приходилось выслеживать ошибку, которая в конечном итоге была вызвана удалением недействительного указателя, не предлагал этого. На поиск таких ошибок уходит несколько часов, и они почти всегда влияют на программу совершенно неожиданным образом, что трудно или невозможно отследить до исходной проблемы.
источник
Важная причина, по которой многие операционные системы используют нулевые биты для представления нулевого указателя, заключается в том, что это означает, что
memset(struct_with_pointers, 0, sizeof struct_with_pointers)
и тому подобное установит все указатели внутриstruct_with_pointers
на нулевые указатели. Это не гарантируется стандартом C, но многие, многие программы предполагают это.источник
В одной из старых машин DEC (я думаю, PDP-8) среда выполнения C защищала бы память первую страницу памяти, так что любая попытка доступа к памяти в этом блоке вызывала исключение.
источник
Выбор значения дозорного устройства является произвольным, и это фактически рассматривается в следующей версии C ++ (неофициально известной как «C ++ 0x», скорее всего, в будущем будет известен как ISO C ++ 2011) с введением ключевое слово -1 для некоторого значения N. Другими словами, адреса обычно рассматриваются как значения без знака. Если бы максимальное значение использовалось в качестве контрольного значения, тогда оно должно было бы варьироваться от системы к системе в зависимости от размера памяти, тогда как 0 всегда является представимым адресом. Он также используется по историческим причинам, поскольку адрес памяти 0 обычно не использовался в программах, и в настоящее время в большинстве операционных систем части ядра загружаются на нижние страницы памяти, и такие страницы обычно защищены таким образом, что если прикосновение (разыменование) программой (сохранение ядра) вызовет ошибку.
nullptr
для представления указателя с нулевым значением. В C ++ значение 0 может использоваться как инициализирующее выражение для любого POD и для любого объекта с конструктором по умолчанию, и оно имеет особое значение присвоения контрольного значения в случае инициализации указателя. Что касается того, почему не было выбрано отрицательное значение, адреса обычно находятся в диапазоне от 0 до 2 Nисточник
Это должно иметь какую-то ценность. Очевидно, вы не хотите наступать на значения, которые пользователь может законно захотеть использовать. Я бы предположил, что, поскольку среда выполнения C предоставляет сегмент BSS для данных с нулевой инициализацией, имеет определенный смысл интерпретировать ноль как значение неинициализированного указателя.
источник
Редко операционная система позволяет вам записывать данные по адресу 0. Обычно специфические для ОС данные помещаются в нехватку памяти; а именно IDT, таблицы страниц и т. д. (Таблицы должны быть в ОЗУ, и их легче разместить внизу, чем пытаться определить, где находится верх ОЗУ.) И никакая ОС в здравом уме не позволит вам редактировать системные таблицы волей-неволей.
Возможно, K&R не думали об этом, когда создавали C, но это (наряду с тем фактом, что 0 == null довольно легко запомнить) делает 0 популярным выбором.
источник
Значение
0
- это особое значение, которое принимает различные значения в определенных выражениях. В случае указателей, как уже неоднократно указывалось, он используется, вероятно, потому, что в то время это был наиболее удобный способ сказать «вставьте сюда значение дозорного по умолчанию». Как постоянное выражение, оно не имеет того же значения, что и побитовый ноль (т. Е. Все биты установлены в ноль) в контексте выражения указателя. В C ++ существует несколько типов, которые не имеют побитового нулевого представления,NULL
например, члена-указателя и указателя на функцию-член.К счастью, C ++ 0x имеет новое ключевое слово «выражение , которое означает , что известный недопустимый указатель , который не также сопоставить побитовый нуль для целочисленных выражений»:
nullptr
. Хотя есть несколько систем, на которые можно настроить таргетинг с помощью C ++, которые позволяют разыменовывать адрес 0 без блокировки, поэтому программист остерегается.источник
В этой ветке уже есть много хороших ответов; вероятно, существует много разных причин для предпочтения значения
0
нулевых указателей, но я собираюсь добавить еще две:источник
Это зависит от реализации указателей в C / C ++. Нет никакой конкретной причины, по которой NULL эквивалентен в присвоениях указателю.
источник
Для этого есть исторические причины, но есть и причины для оптимизации.
Обычно ОС предоставляет процессу страницы памяти, инициализированные значением 0. Если программа хочет интерпретировать часть этой страницы памяти как указатель, то она равна 0, поэтому программе достаточно легко определить, что этот указатель является не инициализирован. (это не так хорошо работает при применении к неинициализированным флеш-страницам)
Другая причина заключается в том, что на многих процессорах очень легко проверить эквивалентность значения 0. Иногда это бесплатное сравнение, выполняемое без каких-либо дополнительных инструкций, и обычно может быть выполнено без необходимости предоставления нулевого значения в другом регистре или как литерал в потоке инструкций для сравнения.
Самые дешевые сравнения для большинства процессоров заключаются в том, что знак меньше 0 и равен 0. (оба знака подразумевают, что знак больше 0 и не равен 0)
Поскольку 1 значение из всех возможных значений должно быть зарезервировано как плохое или неинициализированное, вы также можете сделать его тем, у которого будет самый дешевый тест на эквивалентность плохому значению. Это также верно для символьных строк с завершением '\ 0'.
Если бы вы попытались использовать для этой цели больше или меньше 0, то в конечном итоге вы бы разделили свой диапазон адресов пополам.
источник
Константа
0
используется вместоNULL
потому , что C было сделано некоторыми Cavemen триллионы лет назад,NULL
,NIL
,ZIP
, илиNADDA
бы все сделал гораздо больше смысла , чем0
.На самом деле. Хотя многие операционные системы запрещают вам отображать что-либо на нулевом адресе, даже в виртуальном адресном пространстве (люди поняли, что C - небезопасный язык, и, учитывая, что ошибки разыменования нулевого указателя очень распространены, они решили «исправить» их, запретив код пользовательского пространства для сопоставления со страницей 0; Таким образом, если вы вызываете обратный вызов, но указатель обратного вызова имеет значение NULL, вы не выполните какой-то произвольный код).
Поскольку
0
используемый в сравнении с указателем будет заменен некоторым значением, зависящим от реализации , которое является возвращаемым значением malloc при сбое malloc.Это было бы еще более запутанным.
источник
int
не только был такого же размера, как указатель - во многих контекстах,int
и указатель могли использоваться как взаимозаменяемые. Если подпрограмма ожидала указатель, а единица передала целое число 57, подпрограмма будет использовать адрес с тем же битовым шаблоном, что и число 57. На этих конкретных машинах битовый шаблон для обозначения нулевого указателя был 0, поэтому передача int 0 передаст нулевой указатель.( Пожалуйста, прочтите этот абзац, прежде чем читать сообщение.Я прошу всех, кто заинтересован в чтении этого сообщения, постарайтесь прочитать его внимательно и, конечно, не понижайте его, пока не поймете полностью, спасибо. )
Теперь это вики сообщества, поэтому, если кто-то не согласен с какой-либо концепцией, пожалуйста, измените ее, с четким и подробным объяснением того, что не так и почему, и, если возможно, укажите источники или предоставьте доказательства, которые можно воспроизвести.
Ответ
Вот несколько других причин, которые могут быть основными факторами для NULL == 0
if(!my_ptr)
вместоif(my_ptr==NULL)
.Здесь я хотел бы сказать несколько слов о других ответах
Не из-за синтаксического сахара
Утверждение, что NULL равно нулю из-за синтаксического сахара, не имеет особого смысла, если да, то почему бы не использовать индекс 0 массива для хранения его длины?
На самом деле C - это язык, который больше всего напоминает внутреннюю реализацию, имеет ли смысл говорить, что C выбрал ноль только из-за синтаксического сахара? Они предпочли бы предоставить ключевое слово null (как это делают многие другие языки), а не отображать ноль в NULL!
Таким образом, хотя на сегодняшний день это может быть просто синтаксический сахар, ясно, что первоначальное намерение разработчиков языка C не было для синтаксического сахара, как я покажу далее.
1) Спецификация
Тем не менее, хотя это правда, что спецификация C говорит о константе 0 как о нулевом указателе (раздел 6.3.2.3), а также определяет NULL как определяемую реализацию (раздел 7.19 в спецификации C11 и 7.17 в спецификации C99), Факт остается фактом, что в книге "Язык программирования C", написанной изобретателями C, в разделе 5.4 говорится следующее:
Как видно (из слов «нулевой адрес»), по крайней мере, первоначальным намерением авторов C был адрес ноль, а не постоянный ноль, более того, из этого отрывка следует, что причина, по которой спецификация говорит из Константа ноль, вероятно, не исключает выражение, которое оценивается как ноль, а вместо этого включает целочисленную константу ноль как единственную целочисленную константу, разрешенную для использования в контексте указателя без приведения типов.
2) Резюме
Хотя в спецификации явно не говорится, что нулевой адрес может обрабатываться иначе, чем нулевая константа, в ней не говорится, что нет, и тот факт, что при работе с константой нулевого указателя он не утверждает, что это реализация, определенная как действительно с помощью константы, определенной NULL , вместо того, чтобы заявлять, что она равна нулю, показывает, что может быть разница между нулевой константой и нулевым адресом.
(Однако, если это так, мне просто интересно, почему NULL определяется реализацией, поскольку в таком случае NULL также может быть постоянным нулем, поскольку компилятор в любом случае должен преобразовать все нулевые константы в фактическую реализацию, определенную NULL?)
Однако я не вижу этого в реальных условиях, и на обычных платформах нулевой адрес и постоянный ноль обрабатываются одинаково и выдают одно и то же сообщение об ошибке.
Более того, факт в том, что современные операционные системы фактически резервируют всю первую страницу (диапазон от 0x0000 до 0xFFFF), просто чтобы предотвратить доступ к нулевому адресу из-за указателя NULL в C (см. Http://en.wikipedia.org/wiki/ Zero_page , а также «Windows Via C / C ++ Джеффри Рихтера и Кристофа Насара (опубликовано Microsoft Press)»).
Таким образом, я бы попросил любого, кто заявляет, что он действительно видел это в действии, указать платформу, компилятор и точный код, который он фактически сделал (хотя из-за нечеткого определения в спецификации [как я показал] любой компилятор и платформа может делать все, что хочет).
Однако очевидно, что авторы C не имели этого в виду, и они говорили о «нулевом адресе» и о том, что «C гарантирует, что это никогда не будет действительным адресом», а также «NULL - это просто мнемоника ", ясно показывая, что это первоначальное намерение не было для" синтаксического сахара ".
Не из-за операционной системы
Также утверждается, что операционная система запрещает доступ к нулевому адресу по нескольким причинам:
1) Когда был написан C, такого ограничения не было, как можно увидеть на этой странице википедии http://en.wikipedia.org/wiki/Zero_page .
2) Дело в том, что компиляторы C обращались к нулевому адресу памяти.
Об этом свидетельствует следующий документ BellLabs ( http://www.cs.bell-labs.com/who/dmr/primevalC.html ).
(Фактически, на сегодняшний день (как я цитировал выше ссылки из Википедии и Microsoft Press), причина ограничения доступа к нулевому адресу связана с указателями NULL в C! Так что в конце оказывается, что все наоборот!)
3) Помните, что C также используется для написания операционных систем и даже компиляторов C!
Фактически C был разработан с целью написания с ним операционной системы UNIX, и, как таковой, кажется, нет причин, по которым они должны ограничивать себя от нулевого адреса.
(Аппаратное обеспечение) Объяснение того, как компьютеры (физически) могут получить доступ к нулевому адресу
Есть еще один момент, который я хочу здесь объяснить: как вообще можно ссылаться на нулевой адрес?
Подумайте об этом на секунду, адреса выбираются процессором, а затем отправляются как напряжения на шине памяти, которые затем используются системой памяти для получения фактического адреса, и все же нулевой адрес будет означать отсутствие напряжения. Итак, как физическое оборудование системы памяти обращается к нулевому адресу?
Ответ выглядит так: нулевой адрес является значением по умолчанию, и, другими словами, нулевой адрес всегда доступен системе памяти, когда шина памяти полностью отключена, и, как таковой, любой запрос на чтение или запись без указания фактического адреса (который в случае с нулевым адресом) автоматически обращается к нулевому адресу.
источник