Почему нуль адреса используется для нулевого указателя?

В 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 для нулевого указателя абстракция, другие значения указателя не обязательно. Это приводит меня отправлять другой вопрос о Переполнении стека, я мог когда-либо хотеть получить доступ к нулю адреса?.

119
задан Community 23 May 2017 в 12:02
поделиться

17 ответов

2 балла:

  • только постоянное значение 0 в исходном коде является нулевым указателем - реализация компилятора может использовать любое значение, которое она хочет или нуждается в работающем коде. Некоторые платформы имеют специальное значение указателя, которое является «недопустимым», которое реализация может использовать в качестве нулевого указателя.В FAQ по C есть вопрос «Серьезно, действительно ли какие-либо машины действительно использовали ненулевые нулевые указатели или разные представления для указателей на разные типы?» , который указывает на то, что несколько платформ использовали это свойство 0 как нулевой указатель в источнике C, хотя во время выполнения он представлен по-разному. В стандарте C ++ есть примечание, поясняющее, что преобразование «целочисленного константного выражения со значением ноль всегда дает нулевой указатель, но преобразование других выражений, которые имеют нулевое значение, не должно давать нулевой указатель».

  • отрицательное значение может быть использовано платформой так же, как и адрес - стандарт C просто должен был выбрать что-то для использования для обозначения нулевого указателя, и был выбран ноль. Честно говоря, я не уверен, принимались ли во внимание другие дозорные ценности.

Единственными требованиями к нулевому указателю являются:

  • он гарантированно не равен указателю на фактический объект
  • любые два нулевых указателя будут сравнивать одинаково (C ++ уточняет это так, что это нужно только для указателей к тому же типу)
63
ответ дан 24 November 2019 в 01:51
поделиться

Для этого есть исторические причины, но есть и причины для оптимизации.

Обычно операционная система предоставляет процессу страницы памяти, инициализированные значением 0. Если программа хочет интерпретировать часть этой страницы памяти как указатель, тогда она равна 0, поэтому программе достаточно легко определить, что этот указатель не инициализирован. (это не работает так хорошо, когда применяется к неинициализированным страницам flash)

Другая причина заключается в том, что на многих процессорах очень легко проверить эквивалентность значения 0. Иногда это бесплатное сравнение, выполняемое без каких-либо дополнительных инструкций. необходимо, и обычно может быть выполнено без необходимости предоставления нулевого значения в другом регистре или в виде литерала в потоке инструкций для сравнения.

Самые дешевые сравнения для большинства процессоров заключаются в том, что знак меньше 0 и равен 0. (оба значения подразумевают, что знак больше 0 и не равен 0)

Поскольку 1 значение из всех возможных значений должен быть зарезервирован как плохой или неинициализированный, тогда вы можете сделать его тем, который имеет самый дешевый тест на эквивалентность плохому значению. Это также верно для символьных строк с завершением '\ 0'.

Если бы вы попытались использовать для этой цели больше или меньше 0, то в конечном итоге вы бы разделили свой диапазон адресов пополам.

-1
ответ дан 24 November 2019 в 01:51
поделиться

Значение 0 - это специальное значение, которое принимает различные значения в определенных выражениях.В случае указателей, как уже неоднократно указывалось, он используется, вероятно, потому, что в то время это был наиболее удобный способ сказать «вставьте сюда значение дозорного по умолчанию». Как постоянное выражение, оно не имеет того же значения, что и побитовый ноль (то есть все биты установлены в ноль) в контексте выражения указателя. В C ++ существует несколько типов, которые не имеют побитового нулевого представления NULL , например указатель-член и указатель на функцию-член.

К счастью, в C ++ 0x есть новое ключевое слово для выражения «выражение, которое означает заведомо недопустимый указатель, который также не сопоставляется с побитовым нулем для целочисленных выражений»: nullptr . Хотя есть несколько систем, на которые можно настроить таргетинг на C ++, которые позволяют разыменовывать адрес 0 без блокировки, так что программист остерегается.

0
ответ дан 24 November 2019 в 01:51
поделиться

Редко операционная система позволяет вам писать по адресу 0. Обычно специфичные для ОС данные помещаются в нижний объем памяти; а именно IDT, таблицы страниц и т. д. (Таблицы должны быть в ОЗУ, и их легче прикрепить внизу, чем пытаться определить, где находится верх ОЗУ.) И никакая ОС в здравом уме не позволит вам редактировать системные таблицы волей-неволей.

Возможно, K&R и не думали об этом, когда создавали C, но это (вместе с тем фактом, что 0 == null довольно легко запомнить) делает 0 популярным выбором.

0
ответ дан 24 November 2019 в 01:51
поделиться

Выбор значения указателя произволен, и это фактически решается в следующей версии C++ (неофициально известной как "C++0x", скорее всего, в будущем она будет известна как ISO C++ 2011) введением ключевого слова nullptr для представления указателя с нулевым значением. В C++ значение 0 может быть использовано в качестве инициализирующего выражения для любого POD и для любого объекта с конструктором по умолчанию, и оно имеет специальный смысл присвоения значения sentinel в случае инициализации указателя. Что касается того, почему не было выбрано отрицательное значение, то адреса обычно находятся в диапазоне от 0 до 2N-1 для некоторого значения N. Другими словами, адреса обычно рассматриваются как беззнаковые значения. Если бы в качестве дозорного значения использовалось максимальное значение, то оно должно было бы меняться от системы к системе в зависимости от объема памяти, тогда как 0 всегда является представимым адресом. Оно также используется по историческим причинам, поскольку адрес памяти 0 обычно был непригоден для программ, а в настоящее время в большинстве ОС части ядра загружаются в нижнюю страницу (страницы) памяти, и такие страницы обычно защищены таким образом, что их касание (разыменование) программой (за исключением ядра) приведет к сбою.

1
ответ дан 24 November 2019 в 01:51
поделиться

В одной из старых машин DEC (я думаю, PDP-8) среда выполнения C защищала бы память первую страницу памяти, так что любая попытка доступа к памяти в этом блоке вызвала бы исключение.

1
ответ дан 24 November 2019 в 01:51
поделиться

У него должно быть какое-то значение. Очевидно, вы не хотите наступать на значения, которые пользователь может законно захотеть использовать. Я бы предположил, что, поскольку среда выполнения C предоставляет сегмент BSS для данных с нулевой инициализацией, имеет определенный смысл интерпретировать ноль как значение неинициализированного указателя.

1
ответ дан 24 November 2019 в 01:51
поделиться

Я предполагаю, что магическое значение 0 было выбрано для определения недопустимого указателя, поскольку его можно было проверить с меньшим количеством инструкций. Некоторые машинные языки автоматически устанавливают флаги нуля и знака в соответствии с данными при загрузке регистров, чтобы вы могли проверить нулевой указатель с помощью простых инструкций загрузки и перехода без выполнения отдельной инструкции сравнения.

(Большинство ISA устанавливают флаги только для инструкций ALU, но не загружают. И обычно вы не создаете указатели посредством вычислений, за исключением компилятора, когда анализируете исходный код C . Но, по крайней мере, вы этого не делаете »). Для сравнения нужна произвольная константа ширины указателя.)

На Commodore Pet, Vic20 и C64, которые были первыми машинами, на которых я работал, оперативная память начиналась с местоположения 0, поэтому считывание и запись с использованием нулевой указатель, если вы действительно этого хотели.

5
ответ дан 24 November 2019 в 01:51
поделиться

Исторически сложилось так, что младшая память приложения была занята системными ресурсами. Именно в те времена ноль стал нулевым значением по умолчанию.

Хотя это не обязательно верно для современных систем, по-прежнему плохой идеей является установка значений указателей на что-либо, кроме того, что вам выделила память.

2
ответ дан 24 November 2019 в 01:51
поделиться

Относительно аргумента о том, что указатель не устанавливается на нуль после его удаления, так что будущие удаления "обнаруживают ошибки" ...

Если вы действительно , действительно беспокоит это, тогда лучший подход, который гарантированно работает, - это использовать assert ():


...
assert(ptr && "You're deleting this pointer twice, look for a bug?");
delete ptr;
ptr = 0;
...

Это требует некоторого дополнительного набора текста и одной дополнительной проверки во время отладочных сборок, но он наверняка даст вам то, что вы want: обратите внимание, когда ptr удаляется дважды. Альтернатива, приведенная в обсуждении комментария, не устанавливающая указатель на нуль, что приведет к сбою, просто не гарантирует успеха. Хуже того, в отличие от описанного выше, это может вызвать сбой (или намного хуже!) У пользователя, если одна из этих «ошибок» попадет на полку. Наконец, эта версия позволяет вам продолжать запускать программу, чтобы увидеть, что на самом деле происходит.

Я понимаю, что это не отвечает на заданный вопрос, но я беспокоился, что кто-то, читая комментарии, может прийти к выводу, что считается «хорошей практикой» НЕ устанавливать указатели на 0, если возможно, что они будут отправлены бесплатно. () или удалите дважды. В тех немногих случаях, когда это возможно, НИКОГДА не рекомендуется использовать Undefined Behavior в качестве инструмента отладки. Никто, кому когда-либо приходилось выслеживать ошибку, которая в конечном итоге была вызвана удалением недействительного указателя, не предлагал этого.На поиск таких ошибок уходит часы, и они почти всегда влияют на программу совершенно неожиданным образом, что трудно или невозможно отследить до исходной проблемы.

2
ответ дан 24 November 2019 в 01:51
поделиться

Я думаю, что это просто соглашение. Должно быть какое-то значение для обозначения недействительного указателя.

Вы просто теряете один байт адресного пространства, это редко должно быть проблемой.

Не существует отрицательных указателей. Указатели всегда беззнаковые. Кроме того, если бы они могли быть отрицательными, ваша конвенция означала бы, что вы теряете половину адресного пространства.

3
ответ дан 24 November 2019 в 01:51
поделиться

Но поскольку адресация памяти начинается с 0, разве 0 не является таким же допустимым адресом, как и любой другой?

В некоторых/многих/всех операционных системах адрес памяти 0 является каким-то особенным. Например, он часто отображается на недействительную/несуществующую память, что вызывает исключение при попытке доступа к нему.

Почему отрицательное число не является нулем?

Я думаю, что значения указателей обычно рассматриваются как числа без знака: иначе, например, 32-битный указатель мог бы адресовать только 2 ГБ памяти, а не 4 ГБ.

8
ответ дан 24 November 2019 в 01:51
поделиться

Константа 0 используется вместо NULL , потому что C был создан некоторыми пещерными людьми триллионы лет назад, NULL , NIL , ZIP или NADDA имели бы гораздо больше смысла, чем 0 .

Но поскольку адресация памяти начинается с 0, не является ли 0 таким же допустимым адресом, как любой другой?

Действительно.Хотя многие операционные системы запрещают вам отображать что-либо на нулевом адресе, даже в виртуальном адресном пространстве (люди поняли, что C - небезопасный язык, и, отражая, что ошибки разыменования нулевого указателя очень распространены, они решили «исправить» их, запретив код пользовательского пространства для сопоставления со страницей 0; Таким образом, если вы вызываете обратный вызов, но указатель обратного вызова имеет значение NULL, вы не выполните какой-то произвольный код).

Как можно использовать 0 для обработки нулевых указателей , если это так?

Поскольку 0 , используемый для сравнения с указателем, будет заменен некоторыми конкретными реализациями значение, которое является возвращаемым значением malloc при сбое malloc.

Почему отрицательное число не равно нулю ?

Это еще больше сбивает с толку.

-2
ответ дан 24 November 2019 в 01:51
поделиться

Хотя в языке C для представления нулевого указателя используется 0, имейте в виду, что значение самого указателя может не быть нулем. Однако большинство программистов будут использовать только те системы, где нулевой указатель на самом деле равен 0.

Но почему именно ноль? Ну, это один адрес, который есть в каждой системе. И часто младшие адреса зарезервированы для целей операционной системы, таким образом, это значение работает как недоступное для прикладных программ. Случайное присвоение целочисленного значения указателю с такой же вероятностью закончится нулем, как и все остальное.

3
ответ дан 24 November 2019 в 01:51
поделиться

Вы, должно быть, неправильно понимаете значение постоянного нуля в контексте указателя.

Ни в C, ни в C ++ указатели не могут «иметь нулевое значение». Указатели не являются арифметическими объектами. Они не могут иметь числовых значений, таких как «ноль», «отрицательное значение» или что-либо в этом роде. Так что ваше утверждение о том, что «указатели ... имеют нулевое значение», просто не имеет смысла.

В C и C ++ указатели могут иметь зарезервированное значение нулевого указателя . Фактическое представление значения нулевого указателя не имеет ничего общего ни с какими «нулями». Это может быть что угодно, подходящее для данной платформы. Верно, что на большинстве платформ значение нулевого указателя физически представлено фактическим значением нулевого адреса. Однако, если на какой-то платформе адрес 0 фактически используется для какой-то цели (т.е. вам может потребоваться создать объекты по адресу 0), значение нулевого указателя на такой платформе, скорее всего, будет другим. Он может быть физически представлен, например, как значение адреса 0xFFFFFFFF или как значение адреса 0xBAADBAAD .

Тем не менее, независимо от того, как значение нулевого указателя представлено на данной платформе, в вашем коде вы все равно продолжите обозначать нулевые указатели константой 0 . Чтобы присвоить значение нулевого указателя данному указателю, вы продолжите использовать такие выражения, как p = 0 . Компилятор обязан реализовать то, что вы хотите, и преобразовать это в правильное представление значения нулевого указателя, то есть преобразовать его в код, который поместит значение адреса 0xFFFFFFFF в указатель p , например.

Короче говоря, тот факт, что вы используете 0 в своем волшебном коде для генерации значений нулевого указателя, не означает, что значение нулевого указателя каким-то образом привязано к адресу 0 . 0 , который вы используете в исходном коде, - это просто «синтаксический сахар», который не имеет абсолютно никакого отношения к фактическому физическому адресу, на который «указывает» значение нулевого указателя.

15
ответ дан 24 November 2019 в 01:51
поделиться

IIRC, значение «нулевого указателя» не гарантируется равным нулю. Компилятор переводит 0 в любое "нулевое" значение, подходящее для системы (которое на практике, вероятно, всегда равно нулю, но не обязательно). Тот же перевод применяется всякий раз, когда вы сравниваете указатель с нулем. Поскольку вы можете сравнивать указатели только друг с другом и с этим специальным значением-0, это изолирует программиста от знания чего-либо о представлении системы в памяти. Что касается того, почему они выбрали 0 вместо 42 или что-то подобное, я собираюсь предположить, что это потому, что большинство программистов начинают считать с 0 :) (Кроме того, в большинстве систем 0 - это первый адрес памяти, и они хотели, чтобы это было удобно, поскольку в Практические переводы, которые я описываю, на самом деле происходят редко; язык просто позволяет их).

15
ответ дан 24 November 2019 в 01:51
поделиться

Исторически, адресное пространство, начинающееся с 0, всегда было ПЗУ, используемым для некоторых операционных систем или подпрограмм обработки прерываний низкого уровня, в настоящее время, поскольку все является виртуальным (включая адресное пространство), операционная система может отобразить любое выделение на любой адрес, поэтому она НЕ может специально выделять что-либо по адресу 0.

31
ответ дан 24 November 2019 в 01:51
поделиться