有很多类型的类型转换可以确保赋值工作,例如隐式类型转换(提升)和显式类型转换(截断),但我不确定它如何适用于三元运算符的指针类型转换。
#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*
的类型。
我有两个问题:
我已检查过预处理的代码,NULL
已完全展开到((void*)0)
,为什么((void*)0)
和(void*)((void*)0)
的类型不同?
根据表达式B,为什么编译器将char*
的类型转换为void*
的类型而不是反之亦然?
答案 0 :(得分:1)
我会尝试解释:
0
或(void*)0
是一个空指针常量,由三元条件运算符适当处理:
如果一个操作数是空指针常量,则结果具有类型 另一个操作数; (6.5.15 6)
但是(void*)((void *)0)
是一个空指针,但不是空指针常量(NULL
):
值为0的整型常量表达式,或此类表达式 强制转换为void *,称为空指针常量。如果为null 指针常量被转换为指针类型,由此产生 指针,称为空指针,保证比较不等于a 指向任何对象或函数的指针。 (6.3.2.3 3)
因此,现在,本段适用:
否则,一个操作数是指向void或者是合格版本的指针 void,在这种情况下,结果类型是指向适当的指针 合格版本的void。 (6.5.15 6)
答案 1 :(得分:1)
以下适用:
6.5.15条件运算符
第一个操作数应具有标量类型 下列之一应适用于第二和第三个操作数:
/ - /
- 两个操作数都是指向合格或非限定版本的指针 兼容类型;
- 一个操作数是指针,另一个操作数是空 指针常数;或
- 一个操作数是指向对象类型的指针 另一个是指向合格或不合格版本的void的指针。
在第一种情况下,你有一个操作数(char*)NULL
,它是一个(非限定的)指向类型的指针,一个操作数NULL
是一个空指针常量。
在§6中进一步说,它说:
如果第二个和第三个操作数都是指针,或者一个是null 指针常量而另一个是指针,结果类型是a 指向使用类型的所有类型限定符限定的类型的指针 由两个操作数引用
简单来说:如果一个操作数的类型为char*
而另一个操作数是空指针常量,则结果为char*
。
在第二种情况下,您有一个指向类型的指针和一个void*
。也没有空指针常量(我将在下面进一步解释为什么)。它在同一段中进一步说明(强调我的):
...如果一个操作数是a null指针常量,结果具有另一个操作数的类型; 否则,一个操作数 是指向void或void的限定版本的指针,在这种情况下,结果类型是a 指向适当合格版本的void。
表示第二个?:
操作的结果是void*
类型。这应该回答你的第二个问题。
要回答第一个问题,它与?:
运算符无关,而是关于空指针与空指针常量的“古老C之谜”。
如何扩展宏NULL是实现定义的,它是0
或(void*)
。它保证是一个空指针常量。
There is a difference between a null pointer and a null pointer constant,即空指针可以具有任何实现定义的值,而空指针常量始终为0
或(void*)0
。
6.3.2.2说:
值为0的整型常量表达式,或此类表达式 强制转换为void *,称为空指针常量。如果为null 指针常量被转换为指针类型,由此产生 指针,称为空指针,保证比较不等于a 指向任何对象或函数的指针。
所以(void*)NULL
是一个空指针,但它不是一个空指针常量。因此,条件运算符将其视为void指针。
总结一下:空指针不是条件运算符的特例,只有空指针常量。