Как программа может определить, определен ли NULL с целым числом или типом указателя?

C позволяет определять NULL любую константу константы , другими словами, любое выражение integer constant , которое оценивается как 0, или такое выражение cast в void * . Мой вопрос касается того, действительно ли имеет значение выбор определения, то есть зависит от того, будет ли в противном случае правильная программа зависеть от того, какое определение используется. Для целей этого вопроса я хотел бы игнорировать такие проблемы, как NULL , передаваемый вариационным функциям или функциям, лишенным прототипов, поскольку я уже рассматривал его отдельно. Предположим sizeof NULL == sizeof (void *) и sizeof NULL == sizeof (T) для некоторого целочисленного типа T , так что < code> sizeof недостаточно, чтобы ответить на вопрос, имеет ли тип NULL тип указателя.

Очевидно, что C11 позволяет различать тип NULL или любое другое выражение: ключевое слово _Generic .

C99 также обеспечивает один неясный способ, который кажется надежным:

int null_has_ptr_type()
{
    char s[1][1+(int)NULL];
    int i = 0;
    return sizeof s[i++], i;
}

Существуют ли другие методы, с помощью которых тип NULL может быть определен с помощью соответствующей программы на C? Любая работа в C89?

16
@ouah: Я автор этого конкретного примера, но концепция - это существующие знания, и я не помню источник.
добавлено автор R.., источник
@ouah: У меня нет планов использовать его, кроме как в качестве примера. Реальная полезность этого вопроса является частью оценки того, имеет ли значение NULL значение для совместимости приложений, и если да, то в каких направлениях.
добавлено автор R.., источник
@ouah: У меня нет планов использовать его, кроме как в качестве примера. Реальная полезность этого вопроса является частью оценки того, имеет ли значение NULL значение для совместимости приложений, и если да, то в каких направлениях.
добавлено автор R.., источник
@JonathanLeffler: Этот вопрос помечен C, а не C ++. :-)
добавлено автор R.., источник
Приятная находка, спасибо.
добавлено автор R.., источник
Приятная находка, спасибо.
добавлено автор R.., источник
@Kirilenko: Мне было бы интересно узнать, какой компилятор/версия дал неверный ответ.
добавлено автор R.., источник
@Kirilenko: Мне было бы интересно узнать, какой компилятор/версия дал неверный ответ.
добавлено автор R.., источник
Я только что подтвердил, что gcc 4.6 работает правильно, поэтому проблема должна быть исправлена ​​где-то между 4.4.5 и 4.6.3. В серии 4.5 было рассмотрено много проблем с правильностью, поэтому, вероятно, где-то в этом диапазоне было исправлено. Хорошо знать, что некоторые все еще широко используемые gccs дают неверный результат.
добавлено автор R.., источник
Я только что подтвердил, что gcc 4.6 работает правильно, поэтому проблема должна быть исправлена ​​где-то между 4.4.5 и 4.6.3. В серии 4.5 было рассмотрено много проблем с правильностью, поэтому, вероятно, где-то в этом диапазоне было исправлено. Хорошо знать, что некоторые все еще широко используемые gccs дают неверный результат.
добавлено автор R.., источник
«Я хотел бы игнорировать такие проблемы, как NULL, передаваемые на переменные функции или функции, лишенные прототипов, поскольку я уже рассматривал их отдельно». -> Разве это дело в должности? Какая ссылка, если таковая имеется? (Я бы хотел избежать вопроса об ошибке)
добавлено автор chux, источник
«Я хотел бы игнорировать такие проблемы, как NULL, передаваемые на переменные функции или функции, лишенные прототипов, поскольку я уже рассматривал их отдельно». -> Разве это дело в должности? Какая ссылка, если таковая имеется? (Я бы хотел избежать вопроса об ошибке)
добавлено автор chux, источник
char c = NULL; генерирует предупреждения компилятора, если NULL имеет тип void * (поэтому присутствует листинг).
добавлено автор Jonathan Leffler, источник
Действительно ли работает функция null_has_ptr_type ? Он возвращает 0 в обоих случаях на моем компьютере.
добавлено автор md5, источник
@Mat: Кажется, это правильно. С помощью clang он печатает правильный результат.
добавлено автор md5, источник
@Mat: Кажется, это правильно. С помощью clang он печатает правильный результат.
добавлено автор md5, источник
@R .: gcc 4.4.5 , тогда как clang 2.8 работает так, как ожидалось.
добавлено автор md5, источник
@R .: gcc 4.4.5 , тогда как clang 2.8 работает так, как ожидалось.
добавлено автор md5, источник
@Р. решение C99 действительно приятно, вы автор этого фрагмента кода?
добавлено автор ouah, источник
@Р. и планируете ли вы практически использовать эту функцию null_has_ptr_type или это только для ответа на этот вопрос: ... действительно ли выбор определения имеет значение ... ?
добавлено автор ouah, источник
@Р. и планируете ли вы практически использовать эту функцию null_has_ptr_type или это только для ответа на этот вопрос: ... действительно ли выбор определения имеет значение ... ?
добавлено автор ouah, источник
@Р. решение C99 действительно приятно, вы автор этого фрагмента кода?
добавлено автор ouah, источник
Я получаю то же, что @ Кириленко ...
добавлено автор Oliver Charlesworth, источник
return sizeof s [i ++], i; вернет i.
добавлено автор wildplasser, источник
Вам нужен компилятор C с поддержкой C99, включая VLA и правильную реализацию sizeof в этом случае. Если s является VLA, оценивается i ++ , в противном случае это не так. И s будет VLA, если NULL является указателем, а не int. Или что-то в этом роде ... GCC дает разные предупреждения в обоих случаях. @Kirilenko
добавлено автор Mat, источник
Вам нужен компилятор C с поддержкой C99, включая VLA и правильную реализацию sizeof в этом случае. Если s является VLA, оценивается i ++ , в противном случае это не так. И s будет VLA, если NULL является указателем, а не int. Или что-то в этом роде ... GCC дает разные предупреждения в обоих случаях. @Kirilenko
добавлено автор Mat, источник
@R ..: gcc.gnu.org/gcc-4.4/c99status.html заявляет о «сломанной» поддержке VLA. 4.5 говорит, что все в порядке. (4.6.1 работ.)
добавлено автор Mat, источник
@R ..: gcc.gnu.org/gcc-4.4/c99status.html заявляет о «сломанной» поддержке VLA. 4.5 говорит, что все в порядке. (4.6.1 работ.)
добавлено автор Mat, источник

5 ответы

По вопросу, ответам и комментариям, я думаю, мы установили:

  1. Сцена C11 проста ( _Generic ).
  2. Путь C99 довольно ненадежный из-за ошибок компиляторов.
  3. Подстроечные подходы являются тупиковыми из-за typedef .
  4. Никаких других подходов не найдено.

Таким образом, ответ кажется, что нет надежного метода pre-C11 и, по-видимому, нет действительного метода pre-C99.

6
добавлено

Получите определение строки NULL, а затем выполните полную проверку, как хотите. Вот очень простая мысль:

#define XSTR(x) #x
#define STR(x) XSTR(x)

if (STR(NULL)[0] == '(') {
   ...
}

Но я не знаю, как вы будете обрабатывать __ null , который может выйти из этого.

4
добавлено
@Oli: Нет, на самом деле меня больше интересует, как приложения могут непреднамеренно зависеть от определения.
добавлено автор R.., источник
#define NULL (0) :-)
добавлено автор R.., источник
Это работает для произвольных уровней макроопределения? например #define NULL _NULL1 , #define _NULL1 _NULL2 , #define _NULL2 _NULL3 , ..., #define _NULL999 ​​0 ? Думаю, нет..
добавлено автор R.., источник
@AProgrammer: Ах да, спасибо за разъяснение.
добавлено автор R.., источник
@R ..: Кажется, было установлено, что определение может быть обнаружено в теории. Существует ли практическая потребность в надежном решении?
добавлено автор Oliver Charlesworth, источник
@R .., поскольку вы исключили вариационные функции (где разница может привести к UB), я не вижу правдоподобного случая непреднамеренной зависимости. И я ожидаю, что он будет рассмотрен или, по крайней мере, фольклор к настоящему времени, если бы был один (особенно, что (void *) 0 является общим для реализации C и не соответствует C ++).
добавлено автор AProgrammer, источник
@R .., была причина, по которой я предложил очень простой мыслящий ;
добавлено автор AProgrammer, источник
@R .., это не проблема (аргумент расширен, прежде чем передать его другому макросу), необходимы два уровня, потому что он используется дословно с # и ##.
добавлено автор AProgrammer, источник

Получите определение строки NULL, а затем выполните полную проверку, как хотите. Вот очень простая мысль:

#define XSTR(x) #x
#define STR(x) XSTR(x)

if (STR(NULL)[0] == '(') {
   ...
}

Но я не знаю, как вы будете обрабатывать __ null , который может выйти из этого.

4
добавлено
#define NULL (0) :-)
добавлено автор R.., источник
Это работает для произвольных уровней макроопределения? например #define NULL _NULL1 , #define _NULL1 _NULL2 , #define _NULL2 _NULL3 , ..., #define _NULL999 ​​0 ? Думаю, нет..
добавлено автор R.., источник
@Oli: Нет, на самом деле меня больше интересует, как приложения могут непреднамеренно зависеть от определения.
добавлено автор R.., источник
@AProgrammer: Ах да, спасибо за разъяснение.
добавлено автор R.., источник
@R ..: Кажется, было установлено, что определение может быть обнаружено в теории. Существует ли практическая потребность в надежном решении?
добавлено автор Oliver Charlesworth, источник
@R .., это не проблема (аргумент расширен, прежде чем передать его другому макросу), необходимы два уровня, потому что он используется дословно с # и ##.
добавлено автор AProgrammer, источник
@R .., была причина, по которой я предложил очень простой мыслящий ;
добавлено автор AProgrammer, источник
@R .., поскольку вы исключили вариационные функции (где разница может привести к UB), я не вижу правдоподобного случая непреднамеренной зависимости. И я ожидаю, что он будет рассмотрен или, по крайней мере, фольклор к настоящему времени, если бы был один (особенно, что (void *) 0 является общим для реализации C и не соответствует C ++).
добавлено автор AProgrammer, источник

Не могли бы вы подкрепить макрос и посмотреть на строку?

# include 
# include 
# include 

# define STRINGIFY(x) STRINGIFY_AUX(x)
# define STRINGIFY_AUX(x) #x

int main(void)
{
  const char *NULL_MACRO = STRINGIFY(NULL);

  if (strstr("void", NULL_MACRO) != NULL)
    puts("pointer");
  else
    puts("integer");
}

Он правильно печатает "integer" , если вы добавляете это (обычно NULL имеет тип pinter):

# undef NULL
# define NULL 0

NULL cannot be something like (int) ((void *) 0) because the standards doesn't state that a null pointer constant converted to an integer type is still a null pointer constant.

Кроме того, стандарт также говорит об целочисленных константных выражениях (C11, 6.6/6):

An целочисленное константное выражение 117) должно иметь целочисленный тип и должно иметь только операнды, которые являются целыми константами, константами перечисления, символьными константами, sizeof , результаты которого представляют собой целые константы, выражения _Alignof и плавающие константы, которые являются непосредственными операндами приведений. Операторы Cast в целочисленном постоянном выражении должны преобразовывать только арифметические типы в целые типы, за исключением того, что они являются частью операнда для sizeof или _Alignof .

EDIT: actually this doesn't work with things like:

# define NULL (sizeof (void *) - sizeof (void *))

(спасибо, что заметили), и это не может быть проверено тривиально, как требуется OP, требуется небольшая работа (простой синтаксический анализ).

EDIT 2: and there are also typedef as comment correctly pointed out.

3
добавлено
Теоретически этот подход может работать, но он очень нетривиальен.
добавлено автор R.., источник
На самом деле я не уверен, может ли это работать или нет. См. Мой комментарий к ответу AProgrammer.
добавлено автор R.., источник
@netcoder: Это наоборот. Целочисленное константное выражение со значением 0 по-прежнему является константой нулевого указателя, когда вы передаете его в void * . Но приведение указателя к целочисленному типу никогда не дает целочисленного постоянного выражения.
добавлено автор R.., источник
Ваши аргументы strstr вернутся назад. :-) И проверка глючит. #define NULL (sizeof (void *) - sizeof (void *)) .
добавлено автор R.., источник
@effeffe: Действительно, я думаю, что, строго говоря, использование __ null может быть несоответствующим, поскольку стандарт очень строгий в отношении того, что составляет константу нулевого указателя и, похоже, не позволяет использовать дополнительные определенные для реализации.
добавлено автор R.., источник
По крайней мере, в C, согласно 6.3.2.3, «Целочисленное постоянное выражение со значением 0 или такое выражение, отлитое от типа void *, называется константой нулевого указателя». __ null не является целочисленным постоянным выражением со значением 0, ни одним нажатием на void * , поэтому он не является константой нулевого указателя.
добавлено автор R.., источник
Кстати, я думаю, что проблема на самом деле намного проще, чем я думал. Сначала пропустите все начальные символы ( и пробелы), затем проверьте, что следующий символ v .
добавлено автор R.., источник
Проблема в том, что определение «константа нулевого указателя» очень строгое. Это не «целочисленное постоянное выражение со значением 0, или такое выражение, отличное от void *, или другая константа нулевого указателя, определенная реализацией». Только только , чтобы иметь тип указателя, применяя приведение (void *) непосредственно в качестве конечного оператора к целочисленному постоянному выражению со значением 0.
добавлено автор R.., источник
Да, я думаю, что typedef помещает гвоздь в гроб для этого подхода. Невозможно это исправить.
добавлено автор R.., источник
Но typedef не просто эквивалентный тип, это другое имя для одного и того же типа, поэтому приведение к GenericPointer кастинг в void * .
добавлено автор Daniel Fischer, источник
@effeffe Функция в вопросе работает для соответствия компиляторам C99. Но pre-C99, я не вижу надежного способа выполнения. Конечно, в любой разумной реализации у вас есть либо (void *) 0 , либо 0 [возможно с суффиксом типа в 0 ], так что никакой реальной проблемы. Но если мы понизим ограничение чувствительности ...
добавлено автор Daniel Fischer, источник
@R .. Проверка на 'v' не требуется: typedef void * GenericPointer; .
добавлено автор Daniel Fischer, источник
@DanielFischer, если мы добавим typedef к этой проблеме, я думаю, что нет абсолютно никакого способа получить тип NULL в стандарте C до C11, правильно? Это может быть все.
добавлено автор effeffe, источник
@R .. мой предыдущий комментарий был неправильным. Во всяком случае, вам повезло, нет стандартных целых типов, начинающихся с v :) Я думаю, что ваша идея может работать, и это довольно просто.
добавлено автор effeffe, источник
@R .. # define NULL (1? (Void *) 0: (void *) 0) ? (нет ожидания, может быть, это уже не постоянное выражение, нужно проверить это)
добавлено автор effeffe, источник
@DanielFischer, кстати, действительно законно использовать типы typedef в константе нулевого указателя? В стандарте говорится, что он должен быть целым числом или указателем на void, а не "... или эквивалентным типом" , как он говорит в других частях стандарта, таких как C11-5.1.2.2. 1/p1 о main() ( typedef также упоминается в ноте). Может быть, мне не хватает какого-то «общего правила» где-то в стандарте ... черт возьми, до того, как вы пришли, у нас было стандартное, совместимое с бессмысленным решением. :)
добавлено автор effeffe, источник
@ DanielFischer Я пробовал ... Я сейчас сдаюсь.
добавлено автор effeffe, источник
@AProgrammer там «определенная реализация» означает, что он может быть 0 , (int) 0 , (void *) 0 и т. д., но значение "константа нулевого указателя" четко определено и не приводит к реализации, определяемому реализацией. Определяется реализация , в которой используется вся необходимая константа нулевого указателя.
добавлено автор effeffe, источник
@AProgrammer кажется, что вы правы в C ++ (я попробовал это сейчас), но это действительно соответствует C? Где стандарт C говорит, что это соответствует? Если моя программа получает «__null», которая не является константой нулевого указателя, я думаю, что это незаконно.
добавлено автор effeffe, источник
@R .. спасибо за аргументы ... и позвольте мне подумать о багги-тесте. :) (человек, ты злой, это действительно странно)
добавлено автор effeffe, источник
@AProgrammer, но если в стандарте указано, что макрос должен быть константой нулевого указателя, не должен ли он расширяться до такого выражения в конце? Иначе, разве это не было бы незаконным?
добавлено автор effeffe, источник
@AProgrammer Я думаю, что ответ в C11 6.6/6, я добавлю его к ответу.
добавлено автор effeffe, источник
@R .. макросы полностью расширены, поэтому я думаю, что теперь это может сработать. Но да, это не так тривиально ... на данный момент не удалось найти ничего лучшего.
добавлено автор effeffe, источник
@netcoder нет, если NULL является указателем, не так ли?
добавлено автор effeffe, источник
@AProgrammer хорошая точка ... но как насчет # определить NULL ((int) 0) или (0) , если вы посмотрите на первый символ? :) Спасибо, в любом случае, я исправлю его другим тестом.
добавлено автор effeffe, источник
@effeffe, мне кажется, что утверждение __null является целочисленным постоянным выражением (casted to void * or not) является соответствующим расширением и ничего не мешает использованию расширений в определении стандартных макросов (есть несколько, которые, вероятно, не может быть определена без использования определенных объектов реализации, например, аргументов var args). BTW, GCC использует такое определение для NULL в C ++.
добавлено автор AProgrammer, источник
#define NULL (0 * 0) ;-)
добавлено автор AProgrammer, источник
@effeffe, я не уверен, что это очень хороший момент, это было скорее больше, чем шутка. Я вижу только на практике 3 определения: 0 , 0L и (void *) 0 .
добавлено автор AProgrammer, источник
@R .., говоря, что __ null является константой нулевого указателя, обязательно является соответствующим расширением (стандарт ничего не говорит о __ null , за исключением того, что он зарезервирован для реализации). Тогда я не вижу ничего, препятствуя использованию соответствующего расширения в определении NULL .
добавлено автор AProgrammer, источник
Очевидно, что правдоподобный трудный случай для обработки - #define NULL __null , где __ null является неотъемлемой частью компилятора.
добавлено автор AProgrammer, источник
@R .., стандарт просто говорит, что NULL расширяется до константы константы нулевого указателя реализации , я не вижу, где это мешает реализации рассматривать __ null как реализацию определенная константа нулевого указателя.
добавлено автор AProgrammer, источник

Here's one obscure way: If the program uses the expression &*NULL, this won't compile with NULL having integer type but will if NULL has pointer type.

В C99 есть формулировка для этого особого случая:

If the operand [of the & operator] is the result of a unary * operator, neither that operator nor the & operator is evaluated and the result is as if both were omitted, except that the constraints on the operators still apply and the result is not an lvalue.

The constraints on the operators aren't violated: the operand of & is the result of the unary * operator, and the operand of the unary * operator has pointer type (as we are assuming NULL is defined that way).

0
добавлено
Я думаю, что вопрос ищет что-то, что скомпилирует, будет ли NULL указателем или целым типом. NULL + NULL компилируется, если и только если NULL имеет целочисленный тип.
добавлено автор Keith Thompson, источник