Когда был 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).