Когда макрокоманде NULL не было 0?
Я смутно помню, как читал об этом пару лет назад, но не могу найти ссылки в сети.
Можете ли вы привести пример, когда макрос NULL не расширился до 0?
Изменить для ясности: сегодня он расширяется до ((void *)0)
, (0)
, или же (0L)
, Однако были архитектуры, которые давно забыли, где это не так, и NULL расширился до другого адреса. Что-то вроде
#ifdef UNIVAC
#define NULL (0xffff)
#endif
Я ищу пример такой машины.
Обновление для решения проблем:
Я не имел в виду этот вопрос в контексте действующих стандартов или расстраивать людей с моей неверной терминологией. Однако мои предположения подтвердились принятым ответом:
Более поздние модели использовали [бла], очевидно, как допущение ко всему существующему плохо написанному C-коду, который делал неверные предположения.
Для обсуждения нулевых указателей в текущем стандарте см. Этот вопрос.
7 ответов
В C FAQ есть несколько примеров исторических машин с ненулевыми представлениями NULL.
Из списка часто задаваемых вопросов 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, помеченная архитектура, даже не имеет обычных числовых указателей; он использует пару (в основном несуществующий дескриптор) в качестве нулевого указателя на Си.
В зависимости от используемой "модели памяти", процессоры семейства 8086 (совместимые с ПК) могут использовать 16-битные указатели данных и 32-битные функциональные указатели или наоборот.
Некоторые 64-битные машины Cray представляют int * в младших 48 битах слова; char * дополнительно использует некоторые из старших 16 битов для указания байтового адреса в слове.
Было время, когда он был напечатан как ((void*)0)
или каким-либо другим специфическим для машины способом, где эта машина не использовала комбинацию всех нулевых битов.
Некоторые платформы (определенные машины CDC или Honeywell) имели различную битовую комбинацию для NULL (т. Е. Не все нули), хотя ISO/ANSI исправили это до ратификации C90, указав, что 0
был правильный указатель NULL в исходном коде, независимо от базовой битовой комбинации. От C11 6.3.2.3 Pointers /4
(хотя, как уже упоминалось, эта формулировка восходит к C90):
Целочисленное константное выражение со значением
0
или такое выражение приведено к типуvoid *
, называется константой нулевого указателя.
В компиляторах C он может расширяться до((void *)0)
' (но это не обязательно). Это не работает для компиляторов C++.
Смотрите также C FAQ, в котором есть целая глава о нулевых указателях.
В файле 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. C++ не может использовать ((void*) 0) из-за более строгих правил приведения указателей; стандарт требует, чтобы NULL было равно 0. C допускает другие определения NULL.
Компиляторы C обычно используют ((void *)0)
, Причина проходит NULL
к функциям с переменными аргументами (или теперь редкие, но все еще легальные функции без прототипа). Когда указатели больше, чем int, 0
будет повышен только до int
и, таким образом, не будет правильно читать как указатель.
Компиляторы C++ не могут использовать это определение, потому что C++ не разрешает неявное приведение из void *
(Кастинг 0
к любому указателю в специальном случае). Однако в C++11 появилось новое ключевое слово nullptr
это нулевая константа указателя специального nullptr_t
Тип неявно преобразуемый в любой тип указателя, но не число. Это решает как проблему с переменным аргументом, так и неявное приведение, а также более серьезные проблемы с выбором перегрузки (0
по понятной причине выбирает int
перегрузка по первому указателю). Допустимо определять их самостоятельно для старых компиляторов, и некоторые компиляторы C++ пытались сделать это в прошлом.
NULL
макрос в Си расширяется до реализации определенной константой нулевого указателя. Это может быть что угодно (так как это определяется реализацией), но в контексте указателя эффект всегда такой же, как если бы он расширялся до константы 0
,
Там никогда не было времени в стандартной истории C, когда NULL
расширился до чего-то конкретно нет 0
, если вы не считаете (void *) 0
как "не 0". Но (void *) 0
за NULL
широко используется по сей день.
В современном С, void *pointer = 0;
предназначен для инициализации "указателя", чтобы не указывать ни на что. Это зависит от платформы относительно того, достигается ли это, устанавливая биты "указателя" в ноль.
В прошлом это формальное значение "0" в контексте указателя не было установлено. Необходимо было установить указатель на фактическое значение, которое платформа рассматривала как "нигде не указывающее". Например, платформа может выбрать некоторый фиксированный адрес, который никогда не будет отображен на страницу. В этом случае в старом компиляторе платформа могла бы определить NULL
как:
#define NULL ((void*)0xFFFFF000)
Конечно, сегодня нет причин не определять его как ((void*)0)
,