Namespaces
Variants

Type

From cppreference.net

(另请参阅 算术类型 了解大多数内置类型的详细信息,以及 C库提供的类型相关工具列表 。)

对象 函数 表达式 具有名为 类型 的属性,该属性决定了存储在对象中或由表达式求值的二进制值的解释方式。

目录

类型分类

C 类型系统包含以下类型:

  • 类型 void
  • 基本类型
  • 类型 char
  • 有符号整数类型
  • 标准类型: signed char short int long long long (C99起)
  • 位精确类型: _BitInt ( N ) ,其中N为指定类型表示所用位数的整型常量表达式(包含符号位)。每个N值都对应一个独立类型。
(C23起)
  • 扩展类型:由实现定义,例如 __int128
(C99起)
  • 无符号整数类型
  • 标准: _Bool , (since C99) unsigned char , unsigned short , unsigned int , unsigned long , unsigned long long (since C99)
  • 位精确类型: unsigned _BitInt ( N ) ,其中 N 为指定类型表示位数的整型常量表达式。每个 N 值均对应一个独立类型。此类别包含 unsigned _BitInt ( 1 ) 类型,该类型无对应的位精确有符号整型。
(C23 起)
  • 扩展类型:由实现定义,例如 __uint128
(C99 起)
  • 浮点类型
  • 实数浮点类型: float , double , long double
  • 十进制实浮点类型: _Decimal32 , _Decimal64 , _Decimal128
(自 C23 起)
  • 复数类型: float _Complex , double _Complex , long double _Complex
  • 虚数类型: float _Imaginary , double _Imaginary , long double _Imaginary
(自 C99 起)
  • 派生类型
(C11 起)

对于上述列出的每种类型,可能存在其类型的多个限定版本,分别对应 const volatile restrict 限定符(在限定词语义允许的情况下)的一种、两种或全部三种组合。

类型分组

  • 对象类型 :所有非函数类型的类型
  • 字符类型 char , signed char , unsigned char
  • 整数类型 char 、有符号整数类型、无符号整数类型、枚举类型
  • 实数类型 :整数类型和实浮点类型
  • 算术类型 :整数类型和浮点类型
  • 标量类型 :算术类型、指针类型 ,以及 nullptr_t (C23起)
  • 聚合类型 :数组类型和结构体类型
  • 派生声明符类型 :数组类型、函数类型和指针类型

构造一个完整对象类型,使得其对象表示中的字节数无法用类型 size_t (即 sizeof 运算符的结果类型)表示 ,包括在运行时形成此类可变长度数组类型 (C99起) ,是未定义行为。

兼容类型

在C程序中,指向 不同翻译单元 中同一对象或函数的声明不必使用相同类型,只需使用足够相似的类型——正式称为 兼容类型 。这同样适用于函数调用和左值访问:实参类型必须与形参类型 兼容 ,左值表达式类型必须与被访问对象类型 兼容

类型 T U 是兼容的,如果

  • 它们是相同类型(相同名称或通过 typedef 引入的别名)
  • 它们是兼容无限定类型的完全相同的 cvr 限定版本
  • 它们是指针类型且指向兼容类型
  • 它们是数组类型,且
  • 它们的元素类型兼容,且
  • 若两者都具有常量大小,则该大小相同。注意:未知边界数组与任何具有兼容元素类型的数组均兼容。 变长数组与任何具有兼容元素类型的数组均兼容。 (C99 起)
  • 它们都是结构体/联合体/枚举类型,且
  • (C99) 若其中一个声明带有标签,则另一个也必须使用相同标签进行声明。
  • 若两者均为完整类型,其成员必须在数量上严格对应,以兼容类型声明,并具有匹配的名称。
  • 此外,若两者均为枚举类型,对应成员还必须具有相同的值。
  • 此外,若两者为结构体或联合体,
  • 对应成员必须按相同顺序声明(仅限结构体)
  • 对应的 位域 必须具有相同宽度。
  • 一个是枚举类型,另一个是该枚举的基础类型
  • 它们是函数类型,且
  • 它们的返回类型兼容
  • 两者均使用参数列表,参数数量(包括省略号的使用)相同,且在应用数组到指针和函数到指针的类型调整并去除顶层限定符后,对应参数具有兼容类型
  • 一种是旧式(无参数)定义,另一种具有参数列表,该参数列表不使用省略号且每个参数(经过函数参数类型调整后)与经过默认参数提升后的对应旧式参数兼容
  • 一种是旧式(无参数)声明,另一种具有参数列表,该参数列表不使用省略号,且所有参数(经过函数参数类型调整后)均不受默认参数提升影响
(until C23)

类型 char signed char 不兼容,且与 unsigned char 不兼容。

如果两个声明引用同一对象或函数且未使用兼容类型,则程序的行为是未定义的。

// 翻译单元 1
struct S { int a; };
extern struct S *x; // 与翻译单元2的x兼容,但与翻译单元3的x不兼容
// 翻译单元 2
struct S;
extern struct S *x; // 与两个翻译单元的x都兼容
// 翻译单元 3
struct S { float a; };
extern struct S *x; // 与翻译单元2的x兼容,但与翻译单元1的x不兼容
// 该行为是未定义的
// 翻译单元 1
#include <stdio.h>
struct s { int i; }; // 与 TU3 的 s 兼容,但与 TU2 的不兼容
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++ 中属于无效程序。

复合类型

复合类型可由两个兼容的类型构建而成;它是一种与这两个类型都兼容且满足以下条件的类型:

  • 如果两个类型都是数组类型,则应用以下规则:
  • 如果一个类型是已知常量大小的数组,则复合类型为该大小的数组。
  • 否则,若一个类型是由未求值表达式指定大小的VLA,则要求获取两种类型复合类型的程序将产生未定义行为。
  • 否则,若一个类型是由指定表达式定义大小的VLA,则复合类型为具有该大小的VLA。
  • 否则,若一个类型是未指定大小的VLA,则复合类型为未指定大小的VLA。
(C99起)
  • 否则,两个类型都是未知大小的数组,且复合类型为未知大小的数组。
该复合类型的元素类型是两种元素类型的复合类型。
  • 如果只有一个类型是带有参数类型列表的函数类型(函数原型),则复合类型是带有该参数类型列表的函数原型。
(until C23)
  • 若两个类型均为具有参数类型列表的函数类型,则复合参数类型列表中每个参数的类型即为对应参数的复合类型。

这些规则递归地适用于派生这两个类型的基类类型。

// 给定以下两个文件作用域声明:
int f(int (*)(), double (*)[3]);
int f(int (*)(char *), double (*)[]); // C23: 错误:'f' 的类型冲突
// 最终生成的函数复合类型为:
int f(int (*)(char *), double (*)[3]);

对于在某个作用域中声明的具有内部或外部 链接 的标识符,若该作用域中已存在该标识符的先前声明且可见,且先前声明指定了内部或外部链接,则后续声明中该标识符的类型将成为复合类型。

不完整类型

不完整类型是一种对象类型,其缺乏足够信息以确定该类型对象的大小。不完整类型可能在翻译单元的某个时刻被补全。

以下类型是不完整的:

  • 类型 void 。此类型无法被补全。
  • 未知大小的数组类型。可通过后续指定大小的声明来完成补全。
extern char a[]; // a的类型是不完整的(通常出现在头文件中)
char a[10];      // a的类型现在是完整的(通常出现在源文件中)
  • 未知内容的结构体或联合体类型。可以通过在同一作用域内稍后定义其内容的相同结构体或联合体的声明来完成。
struct node
{
    struct node* next; // 此时 struct node 尚未定义完成
}; // 此时 struct node 已完成定义

类型名称

在除 声明 之外的其他上下文中,可能需要指定类型名称。在这些情况下,会使用 类型名称 ,其语法结构完全等同于由 类型说明符 类型限定符 组成的列表后接 声明符 (参见 声明 ),就如同声明该类型的单个对象或函数时所使用的格式,不同之处在于省略了标识符:

int n; // 声明一个整型变量
sizeof(int); // 使用类型名称
int *a[3]; // 声明一个包含3个整型指针的数组
sizeof(int *[3]); // 使用类型名称
int (*p)[3]; // 声明一个指向包含3个整数的数组的指针
sizeof(int (*)[3]); // 使用类型名称
int (*a)[*] // 声明指向可变长度数组的指针(函数参数中)
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 现在在作用域内

参考文献

  • C23 标准 (ISO/IEC 9899:2024):
  • 6.2.5 类型 (p: TBD)
  • 6.2.6 类型的表示 (p: TBD)
  • 6.2.7 兼容类型与复合类型 (p: TBD)
  • C17 标准 (ISO/IEC 9899:2018):
  • 6.2.5 类型 (页: 31-33)
  • 6.2.6 类型的表示 (页: 31-35)
  • 6.2.7 兼容类型与复合类型 (页: 35-36)
  • C11 标准 (ISO/IEC 9899:2011):
  • 6.2.5 类型 (p: 39-43)
  • 6.2.6 类型表示 (p: 44-46)
  • 6.2.7 兼容类型与复合类型 (p: 47-48)
  • C99标准(ISO/IEC 9899:1999):
  • 6.2.5 类型(页码:33-37)
  • 6.2.6 类型表示(页码:37-40)
  • 6.2.7 兼容类型与复合类型(页码:40-41)
  • C89/C90 标准 (ISO/IEC 9899:1990):
  • 3.1.2.5 类型
  • 3.1.2.6 兼容类型与复合类型

参见

C++ 文档 关于 Type