6.3.2 其他操作数
TIP
这一节解释了几个最常见、也最容易“默认发生却没意识到”的转换:左值转换、数组到指针、函数到函数指针、void 表达式的丢弃语义,以及指针转换。
6.3.2.1 左值、数组和函数设计符
左值是这样一种表达式:它具有某种非
void对象类型,并且可能指代某个对象。若一个左值在求值时并未真正指代某个对象,则行为未定义。当标准说“某个对象具有某种类型”时,这个类型是由用来指代该对象的左值决定的。
可修改左值必须同时满足以下条件:
- 不是数组类型;
- 不是不完整类型;
- 不是
const限定类型; - 如果它是结构体或联合体,则其任何成员(包括递归包含的成员与元素)都不能是
const限定类型。
除以下情形外,一个非数组左值会转换为其所指代对象中存储的值,并且转换后不再是左值,这称为左值转换:
- 作为
sizeof的操作数; - 作为
typeof运算符的操作数; - 作为一元
&的操作数; - 作为
++的操作数; - 作为
--的操作数; - 作为
.运算符的左操作数; - 作为赋值运算符的左操作数。
- 作为
左值转换后得到的值类型如下:
- 若原左值是限定类型,则得到其去限定版本;
- 若原左值还是原子类型,则还要得到其非原子版本;
- 否则类型保持不变。
若该左值是非数组不完整类型,则行为未定义。若它指代的是自动存储期对象,且这个对象原本本可声明为
register(也就是从未取过地址),并且在使用前未初始化,则行为未定义。除以下情形外,类型为“某类型的数组”的表达式会转换成“指向该数组首元素的指针”表达式,并且转换结果不是左值:
- 作为
sizeof的操作数; - 作为
typeof运算符的操作数; - 作为一元
&的操作数; - 作为用于初始化数组的字符串字面量。
- 作为
若该数组对象具有
register存储类,则上述数组到指针的转换行为未定义。函数设计符是具有函数类型的表达式。除以下情形外,类型为“返回某类型的函数”的函数设计符,会转换为“指向返回该类型函数的指针”:
- 作为
sizeof的操作数; - 作为
typeof运算符的操作数; - 作为一元
&的操作数。
- 作为
IMPORTANT
“数组名会退化成指针”不是无条件规则。sizeof arr、&arr、以及用字符串字面量初始化数组时,都不会发生这一步转换。
6.3.2.2 void
void表达式没有可用的值。它的值不得以任何方式使用,也不得对它执行任何隐式或显式转换(转换到void除外)。若把其他类型的表达式按
void表达式求值,则其值或设计符会被丢弃;这种表达式之所以还要计算,只是为了保留其副作用。
6.3.2.3 指针
void *可以与任意对象类型指针相互转换。任意对象类型指针转换为void *再转换回原对象类型指针后,结果必须与原指针比较相等。对任意限定符
q,指向非q限定类型的指针可以转换为指向其q限定版本的指针;原指针与转换后指针保存的值必须比较相等。值为
0的整数常量表达式、这样的表达式再转换成void *、以及预定义常量nullptr,统称为空指针常量。若空指针常量或nullptr_t类型的值转换为某种指针类型,则所得指针称为空指针,并且它保证与任何对象指针或函数指针都不相等。一个空指针转换为另一种指针类型后,结果仍然是该类型的空指针。任意两个空指针比较时都必须相等。
整数可以转换为任意指针类型。除前文对空指针常量已明确规定的情况外,这种转换结果由实现定义;它可能没有正确对齐,可能并不指向该被引用类型的实体,而且在存入对象时还可能产生不确定表示。
任意指针类型都可以转换为整数类型。除前文已特别规定的情况外,结果由实现定义。若该结果无法由目标整数类型表示,则行为未定义;并且标准也不要求这个结果一定落在某种整数类型的值域之内。
指向对象类型的指针可以转换为指向另一对象类型的指针。若结果指针没有针对被引用类型正确对齐,则行为未定义;否则,当把它再转换回原类型时,结果必须与原指针比较相等。
当对象指针转换为字符类型指针时,结果会指向该对象最低地址的字节。随后对该字符指针连续递增,直到对象大小为止,可以依次得到该对象表示中其余字节的指针。
函数类型指针之间也可以相互转换;若转换后再转回原类型,结果必须与原指针比较相等。若使用转换后的类型去调用一个实际并不兼容的函数,则行为未定义。
6.3.2.4 nullptr_t
nullptr_t可以转换为void、bool或任意指针类型;结果分别是void表达式、false或空指针值。空指针常量以及
nullptr_t类型的值,都可以转换为nullptr_t。
WARNING
“能转换”不等于“能安全解引用”或“能安全调用”。标准通常只保证某些往返转换后的比较结果,不保证中间类型下的访问行为一定有效。