Когда макрокоманде NULL не было 0? - PullRequest
32 голосов
/ 08 апреля 2010

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

Можете ли вы привести пример, когда макрос NULL не расширился до 0?

Изменить для ясности: сегодня он расширяется до ((void *)0), (0) или (0L). Однако были архитектуры, которые давно забыли, где это не так, и NULL расширился до другого адреса. Что-то вроде

#ifdef UNIVAC
     #define NULL (0xffff)
#endif

Я ищу пример такой машины.

Обновление для решения проблем:

Я не имел в виду этот вопрос в контексте действующих стандартов или расстраивать людей из-за моей неверной терминологии. Однако мои предположения подтвердились принятым ответом:

Более поздние модели использовали [бла], очевидно, как допущение ко всему существующему плохо написанному коду C, который делал неверные предположения.

Для обсуждения нулевых указателей в текущем стандарте см. этот вопрос .

Ответы [ 7 ]

32 голосов
/ 08 апреля 2010

В FAQ C есть несколько примеров исторических машин с ненулевыми представлениями NULL.

Из Список часто задаваемых вопросов C , вопрос 5.17 :

В: Серьезно, у любых реально используемых машин ненулевой нуль указатели, или разные представления для указателей на разные типы?

A: Серия Prime 50 использовала сегмент 07777, смещение 0 для нуля указатель, по крайней мере, для PL / I. Более поздние модели использовали сегмент 0, смещение 0 для нулевые указатели в C, требующие новых инструкций, таких как TCNP (Test C Null Pointer), по-видимому, в качестве сноски к [сноске] всех существующих плохо написанный код C, который сделал неправильные предположения. Старшая, Первичные машины с адресацией на слова также были известны тем, что требовали большего указатели на байты (char *), чем указатели на слова (int *).

В серии Eclipse MV от Data General три архитектурно поддерживаемые форматы указателей (слово, байт и битовые указатели), два из которых используются компиляторами C: байтовые указатели для char * и void * и word указатели для всего остального. По историческим причинам во время эволюция 32-битной линии MV от 16-битной линии Nova, слово указатели и байтовые указатели имели смещение, косвенность и кольцо Защитные биты в разных местах слова. Проходя несоответствие Формат указателя на функцию привел к сбоям защиты. В конце концов, компилятор MV C добавил много опций совместимости, чтобы попробовать иметь дело с кодом, который имел ошибки несоответствия типа указателя.

Некоторые базовые блоки Honeywell-Bull используют битовую комбинацию 06000 для (внутренние) нулевые указатели.

CDC Cyber ​​180 Series имеет 48-битные указатели, состоящие из кольца, сегмент и смещение. Большинство пользователей (в кольце 11) имеют нулевые указатели 0xB00000000000. Это было обычным делом на старых машинах CDC с одним дополнением к использовать однозначное слово в качестве специального флага для всех видов данных, включая недействительные адреса.

Старая серия HP 3000 использует другую схему адресации для байта адреса, чем для слов адреса; как несколько машин выше поэтому он использует разные представления для char * и void * указатели, чем для других указателей.

Symbolics Lisp Machine, помеченная архитектура, даже не имеет обычные числовые указатели; он использует пару (в основном несуществующий дескриптор) в качестве нулевого указателя C.

В зависимости от используемой «модели памяти», процессоры семейства 8086 (ПК совместимы) могут использовать 16-битные указатели данных и 32-битные функции указатели или наоборот.

Некоторые 64-битные машины Cray представляют int * в младших 48 битах слово; char * дополнительно использует некоторые из старших 16 бит, чтобы указать адрес байта в слове.

3 голосов
/ 08 апреля 2010

Давным-давно было время, когда оно было напечатано как ((void*)0) или каким-либо другим машинно-ориентированным способом, когда эта машина не использовала комбинацию из всех нулевых битов.

Некоторые платформы (некоторые машины CDC или Honeywell) имели различную битовую комбинацию для NULL (т. Е. не все нули), хотя ISO / ANSI исправил это до ратификации C90, указав, что 0 было правильный указатель NULL в исходном коде, независимо от базовой битовой комбинации. От C11 6.3.2.3 Pointers /4 (хотя, как уже упоминалось, эта формулировка восходит к C90):

Целочисленное константное выражение со значением 0 или такое выражение, приведенное к типу void *, называется константой нулевого указателя.

3 голосов
/ 08 апреля 2010

В компиляторах C он может расширяться до '((void *)0)' (но не обязан это делать). Это не работает для компиляторов C ++.

См. Также FAQ по C, в котором есть целая глава по нулевым указателям .

2 голосов
/ 08 апреля 2010

В файле 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.

1 голос
/ 07 ноября 2013

C компиляторы обычно используют ((void *)0). Причина в том, что NULL передается функциям с переменными аргументами (или теперь редкими, но все еще легальными функциями без прототипа). Когда указатели больше, чем int, 0 будет переведен только в int и, следовательно, не будет правильно считываться как указатель.

Компиляторы C ++ не могут использовать это определение, поскольку C ++ не разрешает неявное приведение из void * (приведение 0 к любому указателю осуществляется в специальном регистре). Однако в C ++ 11 введено новое ключевое слово nullptr, которое является константой нулевого указателя специального типа nullptr_t, неявно преобразуемой в любой тип указателя, но не в число. Это решает как проблему с переменным аргументом, так и неявное приведение, а также более серьезные проблемы с выбором перегрузки (0 по очевидной причине выбирает int перегрузку по указателю один). Допустимо определять их самостоятельно для более старых компиляторов, и некоторые компиляторы C ++ пытались сделать это в прошлом.

0 голосов
/ 08 апреля 2010

В современном C void *pointer = 0; предназначено для инициализации «указателя», чтобы не указывать ни на что. Это зависит от платформы относительно того, достигается ли это, устанавливая биты «указателя» в ноль.

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

#define NULL ((void*)0xFFFFF000)

Конечно, сегодня нет причин не определять его как ((void*)0).

0 голосов
/ 08 апреля 2010
Макрос

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

В стандартной истории C никогда не было времени, когда NULL расширялся до чего-то конкретно не 0, если только вы не рассматривали (void *) 0 как "не 0".Но (void *) 0 для NULL широко используется по сей день.

...