6.2.5 类型
IMPORTANT
这一节定义了标准如何分类“类型”。后面所有关于转换、兼容性、对象表示、表达式求值的规则,几乎都依赖这里。
存入对象中的值,或由函数返回的值,其含义由“用于访问它的表达式的类型”决定。(把一个标识符声明为对象时,该标识符本身就是最简单的这种表达式;其类型由该标识符的声明给出。)类型分为两大类:对象类型(描述对象的类型)和函数类型(描述函数的类型)。在翻译单元中的不同位置,一个对象类型可以是不完整类型28)(即缺少足够信息,无法确定该类型对象的大小),也可以是完全类型29)(即已有足够信息)。
声明为
bool类型的对象,必须足以存储false和true。声明为
char类型的对象,必须足以存储基本执行字符集中的任一成员。如果把基本执行字符集中的成员存入char对象,则其值保证为非负。若存入其他字符,则所得值由实现定义,但必须落在该类型可表示值的范围内。标准有 5 种有符号整数类型:
signed char、short int、int、long int和long long int。(这些类型以及其他类型还可以用 6.7.3 中所述的其他形式来指定。)位精确有符号整数类型写作
_BitInt(N),其中N是整数常量表达式,指定用于表示该类型的位数,并且包含符号位。每个不同的N都表示一个不同的类型。30)还可以存在由实现定义的扩展有符号整数类型。31)标准有符号整数类型、位精确有符号整数类型和扩展有符号整数类型,统称为有符号整数类型。32)
signed char类型对象占用的存储量,与普通char类型对象相同。普通int对象的大小,应当符合执行环境体系结构所自然建议的大小,也就是足以容纳头<limits.h>中INT_MIN到INT_MAX范围内任意值的大小。每种有符号整数类型,都有一种对应但不同的无符号整数类型(通过关键字
unsigned指定),它占用相同数量的存储(包括符号信息),并具有相同的对齐要求。bool类型,以及与标准有符号整数类型对应的那些无符号整数类型,统称为标准无符号整数类型。与扩展有符号整数类型对应的那些无符号整数类型,统称为扩展无符号整数类型。除与位精确有符号整数类型对应的无符号整数类型外,还存在unsigned _BitInt(1),它使用 1 位表示该类型。unsigned _BitInt(1)与这些对应的无符号位精确整数类型一起,统称为位精确无符号整数类型。标准无符号整数类型、位精确无符号整数类型和扩展无符号整数类型,统称为无符号整数类型。33)标准有符号整数类型与标准无符号整数类型,统称为标准整数类型;位精确有符号整数类型与位精确无符号整数类型,统称为位精确整数类型;扩展有符号整数类型与扩展无符号整数类型,统称为扩展整数类型。
对于任意两个符号性相同但整数转换等级不同的整数类型(见 6.3.1.1),转换等级较低者的取值范围,是转换等级较高者取值范围的子范围。
有符号整数类型中非负值的范围,是其对应无符号整数类型取值范围的子范围,并且同一个值在这两种类型中的表示相同。34)无符号类型的可表示值范围为
0到2^N - 1(含两端)。涉及无符号操作数的计算永远不会产生溢出,因为无符号类型的算术以模2^N的方式进行。标准有 3 种浮点类型:
float、double和long double。35)float的值集合是double值集合的子集,而double的值集合又是long double值集合的子集。还有 3 种十进制浮点类型:
_Decimal32、_Decimal64和_Decimal128。它们分别对应 ISO/IEC 60559 的decimal32、decimal64和decimal128格式。36)(十进制浮点类型属于条件特性,实现可以不支持;见 6.10.10.4。)标准浮点类型和十进制浮点类型,统称为实浮点类型。
还有 3 种复数类型:
float _Complex、double _Complex和long double _Complex。37)(复数类型属于条件特性,实现可以不支持;见 6.10.10.4。)实浮点类型和复数类型,统称为浮点类型。每种浮点类型都对应一种实类型,而这个对应类型总是某个实浮点类型。对实浮点类型而言,对应类型就是它自己;对复数类型而言,对应类型就是把类型名中的关键字
_Complex删除后得到的类型。每种复数类型的表示和对齐要求,都与一个恰好包含两个对应实类型元素的数组类型相同:第一个元素等于复数的实部,第二个元素等于复数的虚部。
char类型、有符号与无符号整数类型,以及浮点类型,统称为基本类型。基本类型都是完全对象类型。即使实现把两个或多个基本类型定义为具有相同表示,它们仍然是不同的类型。注:实现可以定义新关键字,用来作为指定某种基本类型(或其他任意类型)的替代写法;这并不违反“所有基本类型都彼此不同”的要求。实现定义关键字采用的形式,是 7.1.3 所述的“保留给任意用途的标识符”。
char、signed char和unsigned char这三种类型,统称为字符类型。实现必须把char定义为与signed char或unsigned char二者之一具有相同的取值范围、表示和行为。38)一个枚举由一组具名整数常量值组成。每个不同的枚举都构成一个不同的枚举类型。
char类型、有符号与无符号整数类型,以及枚举类型,统称为整数类型。整数类型和实浮点类型,统称为实类型。整数类型和浮点类型,统称为算术类型。每种算术类型都属于一个类型域:实类型域包含全部实类型,复类型域包含全部复数类型。
void类型包含一个空值集合;它是不完全对象类型,并且不能被补全。可以从对象类型和函数类型递归构造出任意数量的派生类型:
- 数组类型:描述一组连续分配、且非空的对象,这些对象具有某个特定成员对象类型,称为元素类型。只要数组类型被明确写出,元素类型就必须是完全类型。数组类型由其元素类型和元素个数表征。若元素类型为
T,则该数组类型有时称为“T的数组”。 - 结构体类型:描述一组顺序分配、且非空的成员对象(以及在某些情况下的一个不完全数组),每个成员都可以有可选名字,并且类型可能不同。
- 联合体类型:描述一组彼此重叠、且非空的成员对象,每个成员都可以有可选名字,并且类型可能不同。
- 函数类型:描述返回类型已指定的函数。函数类型由其返回类型以及参数个数和参数类型表征。若返回类型为
T,则该函数类型有时称为“返回T的函数”。 - 指针类型:可以从函数类型或对象类型导出;被指向的类型称为引用类型。指针类型描述一种对象,该对象的值为对引用类型某实体的引用。若引用类型为
T,则该指针类型有时称为“指向T的指针”。指针类型本身是完全对象类型。 - 原子类型:由构造
_Atomic(type-name)所指定的类型。(原子类型属于条件特性,实现可以不支持;见 6.10.10.4。)
算术类型、指针类型和
nullptr_t类型,统称为标量类型。数组类型和结构体类型,统称为聚合类型。39)未知大小的数组类型是不完整类型。对于该类型的标识符,如果在后续声明中(具有内部链接或外部链接)指定了大小,它就会变成完全类型。未知内容的结构体或联合体类型(见 6.7.3.4)也是不完整类型;在同一作用域内,如果之后用同一结构体或联合体标记给出其定义内容,则它会对该类型的全部声明完成补全。
一个完全类型的大小必须小于或等于
SIZE_MAX。如果某类型是完全类型,且不是变长数组类型,那么它就是已知常量大小的类型。数组类型、函数类型和指针类型,统称为派生声明符类型。从类型
T出发,对其施加数组类型推导、函数类型推导或指针类型推导,即得到一种从T导出的派生声明符类型。一个类型由其类型类别表征;它要么是派生类型最外层那次推导所对应的类别,要么(如果该类型不是派生类型)就是该类型本身。
到目前为止提到的所有类型,都是无限定类型。每个无限定类型都对应若干个限定版本,40)它们分别对应
const、volatile和restrict这三个限定符的一种、两种或全部三种组合。某类型的限定版本和无限定版本,是不同的类型,但它们属于同一个类型类别,并具有相同的表示和对齐要求。41)数组与其元素类型总被视为具有相同限定。42)除此之外,其他任何派生类型都不会因为其所导出自的那个类型带有限定符,而自动具有限定。此外还有
_Atomic限定符。出现_Atomic限定符,就表示该类型是原子类型。原子类型的大小、表示和对齐,可能与对应无限定类型不同。因此,本文档只有在明确写出“原子的、限定的或无限定的类型”时,才表示允许原子版本和其他限定版本一起出现。若只写“限定的或无限定的类型”,而没有明确提及原子,则不包含原子类型。指向
void的指针,必须与指向字符类型的指针具有相同的表示和对齐要求。41)同样地,指向兼容类型的限定版本或无限定版本的指针,也必须具有相同的表示和对齐要求。所有指向结构体类型的指针彼此之间必须具有相同的表示和对齐要求;所有指向联合体类型的指针彼此之间也必须如此。至于指向其他类型的指针,则未必具有相同的表示或对齐要求。示例 1:写作
float *的类型,其类型是“指向float的指针”。它的类型类别是指针,而不是浮点类型。该类型的const限定版本写作float * const;而写作const float *的类型并不是一个“限定类型”,它的类型是“指向const限定float的指针”,也就是“指向某个限定类型的指针”。示例 2:写作
struct tag (*[5])(float)的类型,其含义是“由 5 个元素构成的数组,数组元素是‘指向函数的指针’,而该函数以float为单一参数并返回struct tag”。它的类型类别是数组。
脚注说明
28)只有在不需要该类型对象大小时,才能使用不完整类型。例如:把某个 typedef 名字声明为结构体或联合体说明符、声明“指向结构体/联合体的指针”,或声明“返回结构体/联合体的函数”时,就不需要对象大小;但在调用或定义这样的函数之前,该说明必须补全。
29)一个类型可以在整个翻译单元中始终是不完全或完全的,也可以在同一翻译单元中的不同位置改变状态。
30)因此,_BitInt(3) 与 _BitInt(4) 不是同一种类型。
31)实现定义关键字的形式,是 7.1.3 所述的“保留给任意用途的标识符”。
32)本文档中凡针对“有符号整数类型”的陈述,除非另有说明,也适用于位精确有符号整数类型和扩展有符号整数类型。
33)本文档中凡针对“无符号整数类型”的陈述,除非另有说明,也适用于位精确无符号整数类型和扩展无符号整数类型。
34)这里要求表示和对齐相同,其意图是使它们在函数实参、函数返回值和联合体成员中具有可互换性。
35)见“未来语言方向”(6.11.1)。
36)ISO/IEC 60559 把 decimal32 规定为一种数据交换格式,并不要求提供算术支持;但 _Decimal32 本身则是一个完整支持算术的类型。
37)虚数类型的规范见附录 G。
38)定义在 <limits.h> 中的 CHAR_MIN 只能取 0 或 SCHAR_MIN 之一,因此可以用它区分 char 究竟等同于哪一种;无论实现做出哪种选择,char 都仍然是一个独立类型,与那两者都不兼容。
39)注意:聚合类型不包括联合体类型,因为联合体对象在任一时刻只能包含一个成员。
40)关于限定数组类型和限定函数类型,见 6.7.4。
41)这里要求表示和对齐相同,其意图是使这些类型在函数实参、函数返回值和联合体成员中具有可互换性。
42)这不适用于 _Atomic 限定符。注意:限定符并不直接作用于数组类型本身,而是影响引用该数组类型的指针类型的转换规则。