Подтвердить что ты не робот

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

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

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


Edit:

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

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

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

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

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

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

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

4b9b3361

Ответ 1

2 балла:

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

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

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

  • он гарантирует сравнение неравного с указателем на фактический объект
  • любые два нулевых указателя будут сравнивать одинаковые (С++ уточняет это так, что это нужно только для указателей на один и тот же тип)

Ответ 2

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

Ответ 3

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

Ответ 4

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

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

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

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

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

Ответ 5

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

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

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

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

Ответ 6

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

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

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

Ответ 7

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

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

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

Ответ 8

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

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

Ответ 9

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

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

Ответ 10

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

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


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

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

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

Ответ 11

Важная причина, по которой многие операционные системы используют для всех нулевых указателей нуль-ноль, равны нулю, заключается в том, что это означает memset(struct_with_pointers, 0, sizeof struct_with_pointers), и аналогичный параметр будет устанавливать все указатели внутри struct_with_pointers на нулевые указатели. Это не гарантируется стандартом C, но многие, многие программы предполагают это.

Ответ 12

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

Ответ 13

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

Ответ 14

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

Ответ 15

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

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

Ответ 16

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

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

Ответ 17

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

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

Ответ 18

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

Ответ 19

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

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

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

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

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

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

Ответ 20

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

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

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

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

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

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

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

Ответ 21

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

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

Ответ

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

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

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

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

Говоря, что 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), чтобы предотвратить доступ к нулевому адресу из-за указателя C NULL (см. http://en.wikipedia.org/wiki/Zero_page, а также "Windows Via C/С++ Джеффри Рихтера и Кристофа Насарра (опубликовано Microsoft Press)" ).

Таким образом, я хотел бы спросить у любого, кто утверждает, что он действительно видел его в действии, чтобы указать платформу, компилятор и точный код, который он на самом деле сделал (хотя из-за неопределенного определения в спецификации [как я показал ] любой компилятор и платформа могут делать все, что захочет).

Однако кажется, что авторы C не имели в виду это, и они говорили о "нулевом адресе" и что "C гарантирует, что он никогда не является действительным адресом", а также "NULL это всего лишь мнемоника", ясно демонстрируя, что это оригинальное намерение было не для "синтаксического сахара".

Не из-за операционной системы

Также заявляя, что операционная система отказывает в доступе к адресу нуль, по нескольким причинам:

1) Когда C было написано, такого ограничения не было, как можно видеть на этом wikipage http://en.wikipedia.org/wiki/Zero_page.

2) Дело в том, что компиляторы C обратились к нулевому адресу памяти.

Это, по-видимому, факт из следующей статьи BellLabs (http://www.cs.bell-labs.com/who/dmr/primevalC.html)

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

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

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

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

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

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

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

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