Почему нулевой адрес используется для нулевого указателя? - PullRequest
117 голосов
/ 03 мая 2010

В C (или C ++ в этом отношении) указатели являются особыми, если они имеют нулевое значение: я советую устанавливать указатели в ноль после освобождения их памяти, потому что это означает, что освобождение указателя снова не опасно; когда я вызываю malloc, он возвращает указатель со значением ноль, если он не может получить мне память; Я все время использую if (p != 0), чтобы убедиться, что переданные указатели действительны и т. Д.

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


Edit:

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

  • Как и все остальное в программировании, это абстракция. Просто константа, не связанная с адресом 0. C ++ 0x подчеркивает это, добавляя ключевое слово nullptr.

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

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

  • Я признаю, что мое отрицательное числовое предложение было немного диким мозговым штурмом. Использование целого числа со знаком для адресов немного расточительно, если оно означает, что кроме нулевого указателя (-1 или чего-либо другого) пространство значений равномерно разделено между положительными целыми числами, которые дают действительные адреса, и отрицательными числами, которые просто напрасны.

  • Если какое-либо число всегда представляется типом данных, то оно равно 0. (Вероятно, 1 тоже. Я думаю об одномразрядном целом числе, которое будет 0 или 1, если оно не подписано, или просто бит со знаком, если подписано, или двухбитовое целое число, которое будет [-2, 1]. Но тогда вы можете просто перейти к 0, равному нулю, и 1, являющемуся единственным доступным байтом в памяти.)

Тем не менее, в моем разуме есть что-то нерешенное. Вопрос переполнения стека Указатель на определенный фиксированный адрес говорит мне, что даже если 0 для нулевого указателя является абстракцией, другие значения указателя не обязательно. Это заставляет меня опубликовать еще один вопрос переполнения стека, Могу ли я когда-нибудь получить доступ к нулевому адресу? .

Ответы [ 21 ]

2 голосов
/ 04 мая 2010

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

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


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

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

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

1 голос
/ 04 мая 2010

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

1 голос
/ 03 мая 2010

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

1 голос
/ 03 мая 2010

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

0 голосов
/ 03 мая 2010

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

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

0 голосов
/ 18 сентября 2015

Это зависит от реализации указателей в C / C ++.Нет конкретной причины, по которой NULL эквивалентен в присваиваниях указателю.

0 голосов
/ 15 июля 2013

В этой теме уже есть много хороших ответов; вероятно, есть много разных причин для предпочтения значения 0 для нулевых указателей, но я собираюсь добавить еще два:

  • В C ++ при инициализации нуля указатель устанавливает его в ноль.
  • На многих процессорах более эффективно установить значение 0 или проверить его на равенство / не равен 0, чем для любой другой константы.
0 голосов
/ 12 июля 2013

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

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

Ответ

Вот несколько других причин, которые могут быть основными факторами для NULL == 0

  1. Тот факт, что ноль является ложным, так что можно сделать непосредственно if(!my_ptr) вместо if(my_ptr==NULL).
  2. Тот факт, что неинициированные глобальные целые по умолчанию инициализируются всеми нулями, и как таковой указатель всех нулей будет считаться неинициализированным.

Здесь я хотел бы сказать слово на другие ответы

Не из-за синтаксического сахара

Сказать, что 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 гарантирует, что ноль никогда не является действительным адресом для данных, поэтому возвращаемое значение ноль может использоваться для сигнализации о ненормальном событии, в данном случае без пробела.

Указатель и целые числа не являются взаимозаменяемыми, ноль является единственным исключением: постоянный ноль может быть назначен указателю, а указатель может сравниваться с постоянным нулем. Символическая константа NULL часто используется вместо нуля, как мнемоника, чтобы более четко указать, что это специальное значение для указателя. NULL определяется в. В дальнейшем мы будем использовать NULL.

Как можно видеть (из слов "нулевой адрес"), по крайней мере, первоначальное намерение авторов C было с нулевым адресом, а не с постоянным нулем, более того, из этого отрывка следует, что причина спецификации говорит о константе ноль, вероятно, не для исключения выражения, которое оценивается как ноль, а вместо этого, чтобы включить целочисленную константу ноль, чтобы быть единственной целочисленной константой, разрешенной для использования в контексте указателя без приведения.

2) Резюме

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

(Однако, если это так, мне просто интересно, почему NULL определяется реализацией, поскольку в таком случае NULL также может быть постоянным нулем, поскольку компилятор в любом случае должен преобразовать все нулевые константы в фактическую реализацию, определенную NULL?)

Однако я не вижу этого в реальных действиях, и на общих платформах нулевой адрес и постоянный ноль обрабатываются одинаково и выдают одно и то же сообщение об ошибке.

Более того, фактом является то, что современные операционные системы фактически резервируют всю первую страницу (в диапазоне от 0x0000 до 0xFFFF), просто чтобы предотвратить доступ к нулевому адресу из-за указателя NULL в C (см. http://en.wikipedia.org/wiki/Zero_page,, а также «Windows через 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 )

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

(На самом деле на сегодняшний день (как я цитировал ссылки выше из википедии и прессы Microsoft) причина ограничения доступа к нулевому адресу - из-за указателей NULL в C! Так что в конце это оказывается другим способом вокруг!)

3) Помните, что C также используется для написания операционных систем и даже компиляторов C!

Фактически C был разработан с целью написания операционной системы UNIX вместе с ним, и, как таковая, похоже, нет причин, по которым они должны ограничивать себя с нулевого адреса.

(аппаратное обеспечение) Объяснение того, как компьютеры (физически) могут получить доступ к нулевому адресу

Есть еще один момент, который я хочу здесь объяснить, как вообще можно ссылаться на ноль адреса?

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

Ответ, как представляется, заключается в том, что нулевой адрес является значением по умолчанию, и другими словами нулевой адрес всегда доступен системе памяти, когда шина памяти полностью отключена, и, как таковой, любой запрос на чтение или запись без указания фактического адрес (как в случае с нулевым адресом) автоматически обращается к нулевому адресу.

0 голосов

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

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

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

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

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

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

Это было бы еще более запутанным.

0 голосов
/ 04 мая 2010

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

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

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

Недорогие сравнения для большинства процессоров: подпись меньше 0 и равно 0. (подпись больше 0 и не равно 0, подразумевается обоими из них)

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...