Как программа может определить, определен ли NULL с целым числом или типом указателя?
C разрешает NULL
определять любую константу нулевого указателя, другими словами, любое целочисленное выражение константы, которое оценивается в 0, или такое выражение, отлитое от void *
. Мой вопрос касается того, действительно ли имеет значение выбор определения, то есть зависит от того, будет ли в противном случае правильная программа зависеть от того, какое определение используется. Для целей этого вопроса я хотел бы игнорировать такие проблемы, как NULL
, передаваемый на переменные функции или функции, лишенные прототипов, поскольку я уже рассматривал их отдельно. Предположим sizeof NULL == sizeof(void *)
и sizeof NULL == sizeof(T)
для некоторого целочисленного типа T
, так что 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?
Ответы
Ответ 1
Через вопрос, ответы и комментарии, я думаю, мы установили:
- Путь C11 прост (
_Generic
).
- Путь C99 довольно ненадежный из-за ошибок компиляторов.
- Стриптизирующие подходы являются тупиковыми из-за
typedef
.
- Никаких других подходов не найдено.
Таким образом, ответ кажется, что нет надежного метода pre-C11 и, по-видимому, никакого действительного метода pre-C99.
Ответ 2
Получите определение строки NULL, а затем выполните полную проверку, как хотите. Вот очень простой подход:
#define XSTR(x) #x
#define STR(x) XSTR(x)
if (STR(NULL)[0] == '(') {
...
}
Но я не знаю, как вы справитесь с __null
, который может выйти из этого.
Ответ 3
Не могли бы вы подкрепить макрос и посмотреть на строку?
# include <stddef.h>
# include <stdio.h>
# include <string.h>
# 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
не может быть чем-то вроде (int) ((void *) 0)
, поскольку стандарты не указывают, что константа нулевого указателя, преобразованная в целочисленный тип, по-прежнему является константой нулевого указателя.
Кроме того, стандарт также говорит об этом с целыми константными выражениями (C11, 6.6/6):
Целочисленное константное выражение 117) должно иметь целочисленный тип и должно иметь только операнды, которые являются целыми константами, константами перечисления, символьными константами, выражениями sizeof
, результаты которых целочисленные константы, выражения _Alignof
и плавающие константы, которые являются непосредственными операндами приведения. Операторы Cast в целочисленном постоянном выражении должны преобразовывать только арифметические типы в целые типы, за исключением того, что они являются частью операнда для оператора sizeof
или _Alignof
.
EDIT: на самом деле это не работает с такими вещами, как:
# define NULL (sizeof(void *) - sizeof(void *))
(спасибо, что заметили), и это невозможно проверить тривиально, как требуется OP, требуется небольшая работа (простой синтаксический анализ).
РЕДАКТИРОВАТЬ 2: и есть typedef
, как правильно указал комментарий.
Ответ 4
Вот один неясный способ: если программа использует выражение &*NULL
, это не будет компилироваться с NULL
с целым типом, но будет, если NULL
имеет тип указателя.
C99 имеет формулировку для этого частного случая:
Если операнд [оператора &
] является результатом унарного *
оператора, ни тот оператор, ни оператор &
не оцениваются и результат, как если бы оба были опущены, за исключением того, что ограничения на операторы все еще применяются, и результат не является lvalue.
Ограничения на операторы не нарушаются: операнд &
является результатом унарного оператора *
, а операнд унарного оператора *
имеет тип указателя (поскольку мы предполагаем NULL
определяется таким образом).