类型
(内建类型上的更多细节参阅算术类型和 C 库所提供的类型相关的工具列表)
对象、函数和表达式拥有称为类型的属性,它确定存储于对象或表达式求值所得的二进制值的转译方式。
类型分类
C 类型系统由下列类型组成:
- 类型 void
- 基本类型
- 类型 char
- 有符号整数类型
- 标准: signed char 、 short 、 int 、 long 、 long long (C99 起)
|
(C99 起) |
- 无符号整数类型
- 标准: _Bool 、 (C99 起) unsigned char 、 unsigned short 、 unsigned int 、 unsigned long 、unsigned long long (C99 起)
|
(C99 起) |
- 浮点类型
- 实浮点类型: float 、 double 、 long double
|
(C23 起) |
|
(C99 起) |
- 派生类型
(C11 起) |
对于上面列出的每个类型,可以存在数种其类型的限定版本,对应 const
、 volatile
和 restrict
限定符的一、二或全部三个组合(在限定符的语义所允许处)。
类型组别
- 对象类型:所有不是函数类型的类型
- 字符类型: char 、 signed char 、 unsigned char
- 整数类型: char 、有符号整数类型、无符号整数类型、枚举类型
- 实数类型:整数类型和实浮点类型
- 算术类型:整数类型和浮点类型
- 标量类型:算术类型和指针类型以及 nullptr_t (C23 起)
- 聚合类型:数组类型和结构体类型
- 派生声明器类型:数组类型、函数类型和指针类型
构造一个对象表示中的字节数不能以 size_t 类型(即 sizeof
运算符的结果类型)表示的完整对象类型,包括在运行时构成这种 VLA 类型, (C99 起)是未定义行为。
兼容类型
C 程序中,在不同翻译单元中涉及同一对象或函数的声明,不必拥有相同类型。它们只需要拥有相似的类型,正式而言即兼容类型。同样的规则应用到函数调用和左值访问;实参类型必须与形参类型兼容,而左值表达式类型必须与被访问对象的类型兼容。
类型 T
与 U
兼容,若
- 它们是同一类型(同名或由 typedef 引入的别名)
- 它们是使用相同的cvr类型限定符限定的类型,并且被限定的类型是兼容的类型
- 它们是指针类型,并指向兼容类型
- 它们是数组类型,且
- 其元素类型兼容,且
- 若都拥有常量大小,则大小相同。注意:未知边界数组与任何兼容元素类型的数组兼容。 VLA 与任何兼容元素类型的数组兼容。 (C99 起)
- 它们都是结构体/联合体/枚举类型,且
- (C99)若一者以标签声明,则另一者必须以同一标签声明。
- 若它们都是完整类型,则其成员必须在数量上准确对应,以兼容类型声明,并拥有匹配的名称。
- 另外,若它们都是枚举,则对应成员亦必须拥有相同值。
- 另外,若它们是结构体或联合体,则
- 对应的元素必须以同一顺序声明(仅结构体)
- 对应的位域必须有相同宽度。
- 一者为枚举类型,而另一者为该枚举的底层类型
- 它们是函数类型,且
- 其返回类型兼容
- 它们都使用参数列表,参数数量(包括省略号的使用)相同,而其对应参数,在应用数组到指针和函数到指针类型调整,及剥除顶层限定符后,拥有相同类型
- 一个是旧式(无参数)定义,另一个有参数列表,参数列表不使用省略号,而每个参数(在函数参数类型调整后)都与默认参数提升后的对应旧式参数兼容
- 一个是旧式(无参数)声明,另一个拥有参数列表,参数列表不使用省略号,而所有参数(在函数参数类型调整后)不受默认参数提升影响
类型 char 既不与 signed char 兼容,也不与 unsigned char 兼容。
若涉及同一对象或函数的二个声明不使用兼容类型,则程序的行为未定义。
// 翻译单元 1(Translation Unit 1,以下简称TU1,下同) struct S {int a;}; extern struct S *x; // 与 TU2 的 x 兼容,但不与 TU3 的 x 兼容 // 翻译单元 2 struct S; extern struct S *x; // 与两个 x 兼容 // 翻译单元 3 struct S {float a;}; extern struct S *x; // 与 TU2 的 x 兼容,但不与 TU1 的 x 兼容 // 行为未定义
// 翻译单元 1 (Translation Unit 1,以下简称TU1,下同) #include <stdio.h> struct s {int i;}; // 与 TU3 的 s 兼容,但不与 TU2 的 s 兼容 extern struct s x = {0}; // 与 TU3 的 x 兼容 extern void f(void); // 与 TU2 的 f 兼容 int main() { f(); return x.i; } // 翻译单元 2 struct s {float f;}; // 与 TU4 的 s 兼容,但不与 TU1 的 s 兼容 extern struct s y = {3.14}; // 与 TU4 的 y 兼容 void f() // 与 TU1 的 f 兼容 { return; } // 翻译单元 3 struct s {int i;}; // 与 TU1 的 s 兼容,但不与 TU2 的 s 兼容 extern struct s x; // 与 TU1 的 x 兼容 // 翻译单元 4 struct s {float f;}; // 与 TU2 的 s 兼容,但不与 TU1 的 s 兼容 extern struct s y; // 与 TU2 的 y 兼容 // 行为良好定义:只有对象或函数的多个声明,而非类型自身必须拥有兼容类型
注意: C++ 没有兼容类型的概念。在不同翻译单元中声明二个兼容但不等同的类型的 C 程序不是合法的 C++ 程序。
合成类型
合成类型能从二个兼容的类型构造;它是与两个类型兼容,并满足下列条件的类型:
- 若两个类型均为数组类型,则应用下列规则:
- 若一个类型是常量大小数组,则合成类型为该大小的数组。
|
(C99 起) |
- 否则,两个数组类型都有未知大小,而合成类型为未知大小的数组。
- 合成类型的元素类型是二个元素类型的合成类型。
- 若一个类型是有参数类型列表(函数原型)的函数类型,则合成类型为有该参数类型列表的函数原型。
- 若两个类型均为有参数类型列表的函数类型,则合成类型的参数类型列表中的每个参数类型,是对应参数的合成类型。
这些规则递归地应用到二个类型的派生来源类型。
// 给定一下二个文件作用域声明: int f(int (*)(), double (*)[3]); int f(int (*)(char *), double (*)[]); // 生成的函数合成类型为: int f(int (*)(char *), double (*)[3]);
对于拥有内部或外部链接,并在其先前声明已经可见的作用域中再次声明的标识符,若先前的声明指定了内部或外部链接,则在后一声明中的标识符类型成为合成类型。
不完整类型
不完整类型是缺乏足以确定其对象大小的信息对象类型。不完整类型可以在翻译单元的某些点完整。
下列类型不完整:
- 类型 void 。此类型不能完整。
- 大小未知的数组。之后指定大小的声明能使之完整。
extern char a[]; // a 的类型不完整(这通常出现于头文件) char a[10]; // a 的类型现在完整(这通常出现于源文件)
- 内容未知的结构体或联合体类型。在同一作用域的后面,定义同一结构体或联合体的内容的声明能使之完整。
struct node { struct node *next; // struct node 在此点不完整 }; // struct node 在此点完整
类型名
可能在异于声明的语境中必须指名类型。这些情况下使用类型名,即 type-specifiers 及 type-qualifiers 后随 declarator 的列表(见声明),它在文法上与会用来声明此类型的单个对象或函数的列表准确相同,除了省略掉标识符:
int n; // 声明 int sizeof(int); // 使用类型名 int *a[3]; // 声明 3 个指向 int 指针的数组 sizeof(int *[3]); // 使用类型名 int (*p)[3]; // 声明指向 3 个 int 的数组的指针 sizeof(int (*)[3]); // 使用类型名 int (*a)[*] // (在函数参数中)声明指向 VLA 的指针 sizeof(int (*)[*]) // (在函数参数中)使用类型名 int *f(void); // 声明函数 sizeof(int *(void)); // 使用类型名 int (*p)(void); // 声明指向函数指针 sizeof(int (*)(void)); // 使用类型名 int (*const a[])(unsigned int, ...) = {0}; // 声明指向函数指针的数组 sizeof(int (*const [])(unsigned int, ...)); // 使用类型名
除了围绕标识符的冗余括号在类型名中有意义,并表示“不指定参数的函数”:
int (n); // 声明 int 类型的 n sizeof(int ()); // 使用类型“返回 int 的函数”
类型名用于下列场合:
(C99 起) | |
(C11 起) |
类型名可引入新类型:
void* p = (void*)(struct X {int i;} *)0; // 用于转型表达式的 "struct X {int i;}*" // 引入新类型 "struct X" struct X x = {1}; // struct X 现在在作用域中
引用
- C17 标准(ISO/IEC 9899:2018):
- 6.2.5 Types (第 31-33 页)
- 6.2.6 Representations of types (第 31-35 页)
- 6.2.7 Compatible type and composite type (第 35-36 页)
- C11 标准(ISO/IEC 9899:2011):
- 6.2.5 Types (第 39-43 页)
- 6.2.6 Representations of types (第 44-46 页)
- 6.2.7 Compatible type and composite type (第 47-48 页)
- C99 标准(ISO/IEC 9899:1999):
- 6.2.5 Types (第 33-37 页)
- 6.2.6 Representations of types (第 37-40 页)
- 6.2.7 Compatible type and composite type (第 40-41 页)
- C89/C90 标准(ISO/IEC 9899:1990):
- 3.1.2.5 Types
- 3.1.2.6 Compatible type and composite type