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


121

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


11
Вы можете так же легко изменить if (p != 0)на if (p)который является общим идиома в C и C ++, хотя вам придется выйти из привычки , если вы берете на Java.
Пол Томблин,

14
Удаление чего-либо дважды означает, что ваш код неправильный. Я не рекомендую устанавливать указатели на null после этого, чтобы вы могли вывести из строя и исправить проблему, а не подавлять ее. В любом случае вы совершаете ошибку, предполагая, что адрес является целым числом. Это не обязательно так, и 0 просто представляет некоторое фактическое значение указателя, которое зависит от реализации. «Отрицательный адрес» концептуально не имеет смысла.
GManNickG

6
@GMan: Может быть, даже неплохо установить указатель на адрес, который вызовет сбой, например 0xDEADBEEF.
Билли Онил

5
Вопрос, который никогда не умрет!

8
@Noah: для точки установлено значение null -> скрыть ошибки программирования, не устанавливать значение null -> найти ошибки программирования. Не знаю, как вы, но хотелось бы, чтобы мой код был правильным.
GManNickG

Ответы:


65

2 балла:

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

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

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

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

12
+1 Я подозреваю, что 0 был выбран просто по историческим причинам. (0 - это начальный и недопустимый адрес в большинстве случаев.) Конечно, в целом такое предположение не всегда верно, но 0 работает очень хорошо.
GManNickG

8
Пространство также могло быть фактором. В дни, когда впервые был разработан C, память была НАМНОГО дороже, чем сейчас. Число ноль можно удобно вычислить с помощью инструкции XOR или без необходимости загружать немедленное значение. В зависимости от архитектуры это может потенциально сэкономить место.
Спарки

6
@GMan - Вы правы. На ранних процессорах нулевой адрес памяти был особенным и имел аппаратную защиту от доступа со стороны запущенного программного обеспечения (в некоторых случаях это было начало вектора сброса, и его изменение могло предотвратить сброс или запуск процессора). Программисты использовали эту аппаратную защиту как форму обнаружения ошибок в своем программном обеспечении, позволяя логике декодирования адреса ЦП проверять наличие неинициализированных или недействительных указателей вместо того, чтобы тратить на это инструкции ЦП. Соглашение сохраняется и по сей день, хотя назначение нулевого адреса могло измениться.
bta

10
16-битный компилятор Minix использовал 0xFFFF для NULL.
Джошуа

3
Во многих встроенных системах 0 - допустимый адрес. Значение -1 (все биты один) также является действительным адресом. Контрольные суммы для ПЗУ трудно вычислить, когда данные начинаются с адреса 0. :-(
Томас Мэтьюз,

31

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


6
Вот и все. Это историческое соглашение, и первые адреса использовались для обработчиков прерываний, поэтому их нельзя использовать для обычных программ. Кроме того, 0 является «пустым», что можно интерпретировать как отсутствие значения / указателя.
TomTom

15

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


5
@Justin: Вы неправильно поняли. Константа 0 всегда является нулевым указателем. @Meador говорит, что возможно, что нулевой указатель (обозначенный константой 0) не соответствует нулевому адресу. На некоторых платформах создание нулевого указателя ( int* p = 0) может создать указатель, содержащий значение 0xdeadbeefили любое другое значение, которое он предпочитает. 0 - это нулевой указатель, но нулевой указатель не обязательно является указателем на нулевой адрес. :)
jalf 03

Указатель NULL - это зарезервированное значение, и в зависимости от компилятора может быть любой битовый шаблон. ПУСТОЙ указатель не означает, что он указывает на адрес 0.
Шарджил Азиз,

3
Но @Jalf, константа 0 не всегда является нулевым указателем. Это то, что мы пишем, когда хотим, чтобы компилятор подставил для нас фактический нулевой указатель платформы . Практически говоря, указатель нулевого обычно делает соответствующий адрес нулевого, хотя, и я интерпретирую вопрос Иоиля , как спрашивать , почему это. В конце концов, предположительно, по этому адресу есть действующий байт памяти, так почему бы не использовать несуществующий адрес несуществующего байта вместо удаления действительного байта из воспроизведения? (Я пишу то, что, как мне кажется, думал Джоэл, а не вопрос, который задаю себе.)
Роб Кеннеди,

@ Роб: Вроде того. Я знаю, что вы имеете в виду, и вы правы, но я тоже :) Постоянное целое число 0 представляет собой нулевой указатель на уровне исходного кода. Сравнение нулевого указателя с 0 дает истину. Присвоение 0 указателю устанавливает этот указатель в нуль. 0 - нулевой указатель. Но фактическое представление нулевого указателя в памяти может отличаться от шаблона нулевого бита. (Во всяком случае, мой комментарий был ответом на теперь удаленный комментарий
@Justin

@jalf @Rob Думаю, вам нужно прояснить некоторые термины. :) Из §4.10 / 1: « Константа нулевого указателя - это целочисленное постоянное выражение rvalue целочисленного типа, значение которого равно нулю. Константа нулевого указателя может быть преобразована в тип указателя; результатом является значение нулевого указателя этого типа и отличается от любого другого значения указателя на объект или указателя на тип функции ".
GManNickG

15

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

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

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

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

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


3
<quote> Указатели не являются арифметическими объектами </quote> Арифметика указателей довольно хорошо определена в C и C ++. Частично требование состоит в том, чтобы оба указателя указывали на одну и ту же композицию. Нулевой указатель не указывает ни на какой составной элемент, поэтому его использование в арифметических выражениях указателя недопустимо. Например, это не гарантируется (p1 - nullptr) - (p2 - nullptr) == (p1 - p2).
Бен Фойгт

5
@Ben Voigt: Спецификация языка определяет понятие арифметического типа . Все, что я говорю, это то, что типы указателей не относятся к категории арифметических типов. Арифметика указателя - это другая и совершенно не связанная с этим история, простое лингвистическое совпадение.
AnT

1
Как предполагается, что кто-то, читающий арифметические объекты, должен знать, что это означает «в смысле арифметических типов», а не «в смысле арифметических операторов» (некоторые из которых можно использовать с указателями) или «в смысле арифметики указателей». Что касается лингвистических совпадений, арифметический объект имеет больше букв, общих с арифметикой указателя, чем арифметические типы . В то же время стандарт не говорит о значении указателя . Исходный плакат, вероятно, означал целочисленное представление указателя, а не значение указателя , и NULLявно не обязательно должен быть представлен нулем
Бен Фойгт

Ну, например, термин скалярные объекты в терминологии C / C ++ - это просто сокращение для объектов скалярных типов (точно так же, как объекты POD = объекты типов POD ). Я использовал термин арифметические объекты точно так же, имея в виду объекты арифметических типов . Я ожидаю, что «кто-то» так это поймет. Тот, кто этого не делает, всегда может попросить разъяснений.
AnT

1
Я работал в системе, где (что касается аппаратного обеспечения) null был 0xffffffff, а 0 был вполне допустимым адресом
pm100

8

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

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

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

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


4
Я закодировал устройство, где нулевой адрес был допустимым адресом и не было защиты памяти. Нулевые указатели также были нулевыми битами; если вы случайно написали в нулевой указатель, то вы взорвали настройки ОС, которые были по нулевому адресу; веселья обычно не последовало.
MM

1
Да: например, на процессоре x86 в незащищенном режиме адрес 0 - это таблица векторов прерываний .
ChrisW

@ChrisW: В незащищенном режиме x86 нулевой адрес, в частности, является вектором прерывания деления на ноль, для записи которого некоторые программы могут иметь вполне законные причины.
supercat 02

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

... доступ к нулевому указателю системного процесса отличается от любого другого, поэтому все нулевые биты, как правило, являются прекрасным адресом для "нуля" даже в системах, где доступ к физическому нулевому местоположению был бы полезным и значимым.
supercat 02

5

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

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

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


3

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

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

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


Примечание: вы фактически не теряете адресное пространство; вы можете получить указатель на адрес 0, выполнив: char *p = (char *)1; --p;. Поскольку поведение нулевого указателя не определено стандартом, эта система может pфактически иметь адрес чтения и записи 0, приращение для присвоения адреса 1и т. Д.
ММ

@MattMcNabb: реализация, в которой нулевой адрес является допустимым аппаратным адресом, может совершенно законно определять поведение char x = ((char*)0);чтения нулевого адреса и сохранения этого значения в x. Такой код привел бы к неопределенному поведению для любой реализации, которая не определяла его поведение, но тот факт, что в стандарте указано, что что-то является неопределенным поведением, никоим образом не запрещает реализациям предлагать свои собственные спецификации того, что он будет делать.
supercat

@supercat ITYM *(char *)0. Это правда, но в моем предложении реализация не должна определять поведение *(char *)0или любые другие операции с нулевым указателем.
MM

1
@MattMcNabb: поведение char *p = (char*)1; --p;было бы определено стандартом только в том случае, если эта последовательность была выполнена после того, как указатель на что-то, кроме первого байта объекта, был приведен к an intptr_t, и результат этого приведения оказался равным 1 , и в этом конкретном случае результат --pвыдаст указатель на байт, предшествующий тому, значение указателя которого при приведении к нему intptr_tбыло возвращено 1.
supercat

3

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

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


3
Более вероятная причина всего этого заключается в том, что: дешево раздавать память, которая предварительно инициализирована нулем, и удобно, чтобы значения в этой памяти представляли что-то значимое, например целое число 0, с плавающей запятой 0.0 и нулевые указатели. Статические данные в C, которые инициализированы нулем / нулем, не должны занимать какое-либо пространство в исполняемом файле и при загрузке отображаются в заполненный нулями блок. Ноль также может получить специальную обработку в машинных языках: простые сравнения нуля, такие как «переход, если равен нулю» и т. Д. MIPS даже имеет фиктивный регистр, который является просто нулевой константой.
Kaz

2

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

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


2

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

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


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

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

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


2

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


1

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


В PDP-8 не было компилятора C. PDP-11 не имел защиты памяти, а VAX был печально известен тем, что молча возвращал 0 на разыменование указателя NULL. Я не уверен, к какой машине идет речь.
fuz 06

1

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


1

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


0

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

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


Это неверно в защищенном режиме, и на самом деле в некоторых конфигурациях Linux вы можете писать по виртуальному адресу 0.
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳

0

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

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


0

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

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

0

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


-1

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

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

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

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

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

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


-2

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

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

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

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

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

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

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


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

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

-4

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

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

Ответ

Вот несколько других причин, которые могут быть основными факторами для 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 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 ).

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

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

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

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

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

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

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

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


1
Я не голосовал против вас, но в вашем сообщении есть несколько фактических неточностей, например. эта физическая память по смещению 0 недоступна (из-за того, что все переключатели выключены? Действительно?), 0 и константа 0 являются взаимозаменяемыми (они могут не быть) и другие.
Hasturkun

Что касается 0 и постоянного нуля, это то, что говорится в оригинальной книге, и это то, что показывают фактические испытания, вы обнаружили реальную разницу между ними? Если да, то какой компилятор и платформа? Хотя многие ответы предполагают, что есть разница, я не нашел ее, и у них нет ссылки, чтобы показать разницу. Фактически, согласно en.wikipedia.org/wiki/Zero_page, а также «Windows через C / C ++ Джеффри Рихтера и Кристофа Насара (опубликовано Microsoft Press)», вся первая страница! защищен в современных компьютерах только для предотвращения нуля (на самом деле потеря более одного байта!)
Йоэль Халб

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

Я не согласен с вашими утверждениями. Я тоже не заинтересован в продолжении этого обсуждения.
Hasturkun

6
Аппаратные претензии - ерунда. Чтобы прочитать нулевой адрес, запустите! Chip Select low,! RAS high,! CAS low,! WE high, и все адресные строки имеют низкий уровень. Когда автобус выключен, уровень! CS высокий.
MSalters
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.