C中三元运算符采用不同操作数类型的原理

Principle of different operand types adoption for ternary operator in C

有很多种类型转换来确保赋值工作,例如隐式类型转换(提升)和显式类型转换(截断),但我不确定它如何在三元运算符的指针类型转换上工作。

#include <stdlib.h>

int main (void)
{
    (void)((rand() ? (char*)NULL :        NULL) + 1);
    /*     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        -> expression A */

    /* GCC Warning: pointer of type ‘void *’ used in arithmetic */
    (void)((rand() ? (char*)NULL : (void*)NULL) + 1);
    /*     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        -> expression B */

    return 0;
}

显然,编译器将表达式 A 视为 char* 的类型,而将 B 视为 void* 的类型。

我有两个问题:

我会尝试解释一下:

0(void*)0 是空指针常量,由三元条件运算符适当处理:

if one operand is a null pointer constant, the result has the type of the other operand; (6.5.15 6)

但是(void*)((void *)0)是一个空指针,而不是空指针常量(NULL):

An integer constant expression with the value 0, or such an expression cast to type void *,is called a null pointer constant. If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function. (6.3.2.3 3)

因此,此段现在适用:

otherwise, one operand is a pointer to void or a qualified version of void, in which case the result type is a pointer to an appropriately qualified version of void. (6.5.15 6)

以下适用:

6.5.15 Conditional operator

The first operand shall have scalar type.
One of the following shall hold for the second and third operands:

/--/

— both operands are pointers to qualified or unqualified versions of compatible types;
— one operand is a pointer and the other is a null pointer constant; or
— one operand is a pointer to an object type and the other is a pointer to a qualified or unqualified version of void.

在第一种情况下,您有一个操作数 (char*)NULL,它是一个(非限定的)类型指针,还有一个操作数 NULL,它是一个空指针常量。

在 §6 中更进一步说:

If both the second and third operands are pointers or one is a null pointer constant and the other is a pointer, the result type is a pointer to a type qualified with all the type qualifiers of the types referenced by both operands

用简单的英语来说:如果一个操作数是 char* 类型,另一个是空指针常量,则结果是 char*.

在第二种情况下,您有一个指向类型的指针和一个 void*。空指针常量也不是(我将在下面进一步解释原因)。在同一段落的下方,它说(强调我的):

... if one operand is a null pointer constant, the result has the type of the other operand; otherwise, one operand is a pointer to void or a qualified version of void, in which case the result type is a pointer to an appropriately qualified version of void.

表示第二个?:操作的结果是void*类型。这应该回答你的第二个问题。


要回答你的第一个问题,它与 ?: 运算符本身无关,而是关于空指针与空指针常量的 "ancient C mystery"。

如何扩展 NULL 宏是实现定义的,它是 0(void*)。它保证是一个空指针常量。

,即空指针可以具有任何实现定义的值,而空指针常量始终是 0(void*)0.

6.3.2.2 说:

An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant. If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.

所以(void*)NULL是空指针,但不是空指针常量。因此,条件运算符将其视为空指针。

总结一下:空指针不是条件运算符的特例,只有空指针常量才是。