Когда был NULL макрос не 0?
Я смутно помню, как читал об этом пару лет назад, но я не могу найти ссылку в сети.
Можете ли вы привести мне пример, когда макрос NULL не расширялся до 0?
Изменить для ясности: сегодня он расширяется до ((void *)0)
, (0)
или (0L)
. Однако были давно забыты архитектуры, где это было неверно, и NULL расширился до другого адреса. Что-то вроде
#ifdef UNIVAC
#define NULL (0xffff)
#endif
Я ищу пример такой машины.
Обновить для решения проблем:
Я не имел в виду этот вопрос в контексте нынешних стандартов или не расстраивал людей своей неправильной терминологией. Однако мои предположения были подтверждены принятым ответом:
Более поздние модели использовали [blah], очевидно, как сосок для всего сохранившегося плохо написанного кода на C, который сделал неправильные предположения.
Для обсуждения нулевых указателей в текущем стандарте см. этот вопрос.
Ответы
Ответ 1
В C FAQ есть несколько примеров исторических машин с ненулевыми представлениями.
Из списка часто задаваемых вопросов C, вопрос 5.17:
В: Серьезно, на каких-то реальных машинах действительно использовались ненулевые нулевые указатели или разные представления для указателей на разные типы?
A: Серия Prime 50 использовала сегмент 07777, смещение 0 для нулевого указателя, по крайней мере для PL/I. Более поздние модели использовали сегмент 0 со смещением 0 для нулевых указателей в C, что требовало новых инструкций, таких как TCNP (тестовый нулевой указатель C), очевидно, в качестве сопутствующего элемента для [сноски] всего существующего плохо написанного кода C, который делал неверные предположения. Старые машины Prime с адресацией на слова также были известны тем, что требовали указателей большего размера (char *
), чем указатели на слова (int *
).
Серия Eclipse MV от Data General имеет три архитектурно поддерживаемых формата указателей (слово, байт и битовые указатели), два из которых используются компиляторами C: байтовые указатели для char *
и void *
и указатели слов для всего остального. По историческим причинам во время эволюции 32-битной линии MV из 16-битной линии Nova указатели слов и указатели байтов имели биты смещения, косвенности и защиты кольца в разных местах слова. Передача несоответствующего формата указателя в функцию привела к сбоям защиты. В конце концов, компилятор MV C добавил множество опций совместимости, чтобы попытаться справиться с кодом, в котором были ошибки несоответствия типов указателей.
Некоторые мэйнфреймы Honeywell-Bull используют битовую комбинацию 06000 для (внутренних) нулевых указателей.
Серия CDC Cyber 180 имеет 48-битные указатели, состоящие из кольца, сегмента и смещения. Большинство пользователей (в кольце 11) имеют нулевые указатели 0xB00000000000. На старых CDC-машинах с одним дополнением часто использовалось слово "все-один-бит" в качестве специального флага для всех типов данных, включая недопустимые адреса.
Старая серия HP 3000 использует другую схему адресации для байтовых адресов, чем для адресных слов; как и несколько машин выше, он использует разные представления для указателей char *
и void *
чем для других указателей.
Symbolis Lisp Machine, помеченная архитектура, даже не имеет обычных числовых указателей; он использует пару <NIL, 0>
(в основном несуществующий дескриптор <object, offset>
) в качестве нулевого указателя на Си.
В зависимости от используемой "модели памяти", процессоры семейства 8086 (совместимые с ПК) могут использовать 16-битные указатели данных и 32-битные функциональные указатели или наоборот.
Некоторые 64-битные машины Cray представляют int *
в младших 48 битах слова; char *
дополнительно использует некоторые из старших 16 бит, чтобы указать адрес байта в слове.
Ответ 2
В компиляторах C он может расширяться до '((void *)0)
' (но не обязательно). Это не работает для компиляторов С++.
См. также FAQ C, который содержит целую главу нулевые указатели.
Ответ 3
Было время, когда оно было напечатано как ((void*)0)
или какой-либо другой машинный способ, когда эта машина не использовала шаблон с нулевым битом.
Некоторые платформы (некоторые устройства CDC или Honeywell) имели разные битовые шаблоны для NULL (т.е. не все нули), хотя ISO/ANSI фиксировали, что до того, как C90 был ратифицирован, указав, что 0
был правильным указателем NULL в исходный код, независимо от базовой диаграммы бит. Из C11 6.3.2.3 Pointers /4
(хотя, как уже упоминалось, эта формулировка полностью возвращается к C90):
Целочисленное константное выражение со значением 0
или такое выражение, которое применяется к типу void *
, называется константой нулевого указателя.
Ответ 4
В файле GNU libio.h:
#ifndef NULL
# if defined __GNUG__ && \
(__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 8))
# define NULL (__null)
# else
# if !defined(__cplusplus)
# define NULL ((void*)0)
# else
# define NULL (0)
# endif
# endif
#endif
Обратите внимание на условную компиляцию на __cplusplus. С++ не может использовать ((void *) 0) из-за его более строгих правил о кастовании указателя; для стандарта требуется, чтобы NULL был равен 0. C допускает другие определения NULL.
Ответ 5
Компиляторы C обычно используют ((void *)0)
. Причина заключается в передаче NULL
в функции с переменными аргументами (или теперь редкие, но все же правовые функции без прототипа). Когда указатели больше, чем int, 0
будет продвигаться только до int
и, следовательно, не будет правильно читать в качестве указателя.
Компиляторы С++ не могут использовать это определение, потому что С++ не допускает неявного перевода из void *
(приведение 0
к любому указателю имеет специальную обложку). Однако С++ 11 представила новое ключевое слово nullptr
, которое является константой нулевого указателя специального типа nullptr_t
, неявно конвертируемой в любой тип указателя, но не числа. Это решает как проблему вариационного аргумента, так и неявные литые и еще более серьезные проблемы с выбором перегрузки (0
по понятной причине выбирает int
перегрузку по указателю один). Легально определять их сами для более старых компиляторов, и некоторые компиляторы С++ пытались это сделать в прошлом.
Ответ 6
NULL
макрос в C расширяет до определенной константы нулевой указатель реализации. Это может быть что угодно (поскольку оно определено реализацией), но в контексте указателя эффект всегда такой же, как если бы он был расширен до константы 0
.
В стандартной истории C никогда не было времени, когда NULL
расширилось до чего-то конкретно не 0
, если вы не считаете (void *) 0
как "не 0". Но (void *) 0
для NULL
широко используется и по сей день.
Ответ 7
В современном C, void *pointer = 0;
предназначен для инициализации "указателя", чтобы ничего не указывать. Это зависит от платформы, независимо от того, выполняется ли это путем установки битов "указателя" на все ноль.
В прошлом это формальное значение "0" в контексте указателя не было установлено. Необходимо было установить указатель на фактическое значение, которое платформа обрабатывала как "не указывает нигде". В качестве примера платформа может выбрать какой-либо фиксированный адрес, который никогда не будет отображать на него страницу. В этом случае в старом компиляторе платформа могла бы определить NULL
как:
#define NULL ((void*)0xFFFFF000)
Конечно, сегодня нет причин не определять его как ((void*)0)
.