Я читал, что в "большинстве реализаций" NULL указывает на 0, что бы это ни значило.
Нет, это равно 0;это не указатель на ничего.
В источнике C (void*)0
всегда равен NULL, но реализациям разрешено внутренне использовать другой ненулевой битовый шаблон для представления объектаint *p = NULL;
.Реализации, которые выбирают ненулевой битовый шаблон, должны преобразовываться во время компиляции.(И перевод only работает во время компиляции для целочисленных константных выражений времени компиляции со значением ноль, которые появляются в контексте указателя, а не для memset
или чего-либо еще.) FAQ по C ++ имеет целоераздел по указателям NULL .(В данном случае это также относится к C.)
(В C допустимо обращаться к битовой структуре объекта с memcpy в целое число или с псевдонимом (char*)
на него, так что это возможночтобы обнаружить это в правильно сформированной программе, свободной от неопределенного поведения. Или, конечно, посмотрев содержимое asm или памяти с помощью отладчика! На практике вы можете легко проверить, что правильный asm для NULL, скомпилировав int*foo(){return NULL;}
)
См. Также Почему нулевой адрес используется для нулевого указателя? для дополнительной информации.
Почему существует неопределенность?Что эквивалентно NULL в наборе команд x86?
Во всех соглашениях / ABI о вызовах x86 битовый шаблон asm для указателей NULL является целым числом 0 .
Итак, push 0
или xor edi,edi
(RDI = 0) всегда то, что вам нужно для x86 / x86-64. (Современные соглашения о вызовах, включая все соглашения x86-64, передают аргументы в регистрах.) Ответ
@ J ... показывает, как выдвигать аргументы в порядке справа налево для соглашения о вызовах, которое вы используете , что приводит к первому (крайнему левому)) arg по самому низкому адресу.
На самом деле вы можете хранить их в стеке так, как вам нравится (например, с mov
), до тех пор, пока они не окажутся в нужном месте при запуске call
.
Стандарт C позволяет ему быть другим, потому что реализации C на некотором оборудовании могут захотеть использовать что-то другое, например, специальный битовый шаблон, который всегда дает сбой при разыменовании, независимо от контекста.Или, если 0
было действительным значением адреса в реальных программах, лучше, если p==NULL
всегда ложно для действительных указателей.Или любая другая непонятная аппаратная причина.
Так что да могло бы быть некоторыми реализациями C для x86, где (void*)0
в источнике C превращается в ненулевое целое число в asm,Но на практике их нет.(И большинство программистов рады, что memset(array_of_pointers, 0, size)
фактически устанавливает их в NULL, что основывается на том, что битовый шаблон 0
, потому что некоторые программы делают это предположение, не думая о том факте, что он не гарантированно будет переносимым).
Это не сделано на x86 ни в одном из стандартных C ABI.(ABI - это набор вариантов реализации, которым следуют все компиляторы, чтобы их код мог вызывать друг друга, например, согласовывать структуру структуры, соглашения о вызовах и что означает p == NULL
.)
Я не знаюлюбые современные реализации C, которые используют ненулевые NULL
на других 32- или 64-битных процессорах;виртуальная память позволяет легко избежать адреса 0.
http://c -faq.com / null / machexamp.html имеет несколько исторических примеров:
В серии Prime 50 используется сегмент 07777
, смещение 0
для нулевого указателя, по крайней мере, для PL / I.Более поздние модели использовали сегмент 0
со смещением 0 для нулевых указателей в C, что требовало новых инструкций, таких как TCNP
(Test C Null Pointer), очевидно, в качестве сопроводительной ссылки для [сноски] всего существующего плохо написанного кода C, который сделал неправильнымпредположения.Старые машины Prime с адресацией по словам также были известны тем, что требовали указателей большего размера (char *
), чем указатели слова (int *
).
... см. ссылку для большего количества машини сноска из этого абзаца.
https://www.quora.com/On-which-actual-architectures-is-Cs-null-pointer-not-a-binary-zero-all-bits-zero сообщает о нахождении ненулевого NULL на 286 Xenix, я думаю, используя сегментированные указатели.
Современные операционные системы x86 гарантируют, что процессы не могут сопоставить что-либо с самой нижней страницей виртуального адресного пространства, поэтому разыменование нулевого указателя всегда дает сбой, что облегчает отладку.
например. Linux по умолчанию резервирует 64 кБ адресного пространства. Это помогает, независимо от того, пришло ли оно из NULL-указателя в источнике или какая-то другая ошибка обнуляет указатель целочисленными нулями. 64 КБ вместо страниц с низким 4 КБ индексирует указатель в виде массива, например p[i]
с малыми и средними i
значениями.
Забавный факт: Windows 95 отображает самые младшие страницы виртуального адресного пространства в пространстве пользователя на первые 64 КБ физической памяти, чтобы обойти ступенчатую ошибку 386 B1. Но, к счастью, он смог все настроить, поэтому доступ из нормального 32-битного процесса дал сбой. Тем не менее, 16-битный код, работающий в режиме DOS Compat, может очень легко уничтожить всю машину.
См. https://devblogs.microsoft.com/oldnewthing/20141003-00/?p=43923 и https://news.ycombinator.com/item?id=13263976