В C существует один и только один контекст, в котором необходимо явно привести константу нулевого указателя к определенному типу указателя, чтобы программа работала правильно. Этот контекст передает нулевой указатель через нетипизированный список аргументов функции. В modern C это происходит только тогда, когда вам нужно передать нулевой указатель на функцию, которая принимает переменное число аргументов. (В устаревшем C это происходит с любой функцией, не объявленной с прототипом.) Примером парадигмы является execl
, где самый последний аргумент должен быть нулевым указателем, явно приведенным к (char *)
:
execl("/bin/ls", "ls", "-l", (char *)0); // correct
execl("/bin/ls", "ls", "-l", (char *)NULL); // correct, but unnecessarily verbose
execl("/bin/ls", "ls", "-l", 0); // undefined behavior
execl("/bin/ls", "ls", "-l", NULL); // ALSO undefined behavior
Да, последний пример имеет неопределенное поведение , даже если NULL
определено как ((void *)0)
, потому что void *
и char *
являются , а не , неявно взаимозаменяемыми при передаче через нетипизированный список аргументов, даже если они есть везде.
«Под капотом», проблема здесь в , а не только с битовой комбинацией, используемой для нулевого указателя, но в том, что компилятору может понадобиться знать точный конкретный тип каждого аргумента, чтобы установить правильно создать кадр вызова. (Рассмотрим MC68000 с его отдельными адресами и регистрами данных; некоторые ABI указали аргументы указателя, которые должны быть переданы в адресных регистрах, но целочисленные аргументы в регистрах данных. Рассмотрим также любой ABI, где int
и void *
не имеют одинаковый размер. в наши дни это невероятно редко, но C по-прежнему явно указывает, что void *
и char *
не имеют одинаковый размер.) Если есть прототип функции, компилятор может использовать его, но функции без прототипов и аргументы с переменными параметрами не оказывают такой помощи.
C ++ более сложный, и я не могу объяснить, как это сделать.