类型

来自cppreference.com
< c‎ | language

(内建类型上的更多细节参阅算术类型和 C 库所提供的类型相关的工具列表

对象函数表达式拥有称为类型的属性,它确定存储于对象或表达式求值所得的二进制值的转译方式。

类型分类

C 类型系统由下列类型组成:

  • 类型 void
  • 基本类型
  • 类型 char
  • 有符号整数类型
  • 标准: signed charshortintlonglong long (C99 起)
  • 扩展:实现定义,例如 __int128
(C99 起)
  • 无符号整数类型
  • 标准: _Bool (C99 起) unsigned charunsigned shortunsigned intunsigned longunsigned long long (C99 起)
  • 扩展:实现定义,例如 __uint128
(C99 起)
  • 浮点类型
  • 实浮点类型: floatdoublelong double
  • 十进制实浮点类型: _Decimal32_Decimal64_Decimal128
(C23 起)
  • 复数类型: float _Complexdouble _Complexlong double _Complex
  • 虚数类型: float _Imaginarydouble _Imaginarylong double _Imaginary
(C99 起)
  • 派生类型
(C11 起)

对于上面列出的每个类型,可以存在数种其类型的限定版本,对应 constvolatilerestrict 限定符的一、二或全部三个组合(在限定符的语义所允许处)。

类型组别

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

构造一个对象表示中的字节数不能以 size_t 类型(即 sizeof 运算符的结果类型)表示的完整对象类型,包括在运行时构成这种 VLA 类型, (C99 起)是未定义行为。

兼容类型

C 程序中,在不同翻译单元中涉及同一对象或函数的声明,不必拥有相同类型。它们只需要拥有相似的类型,正式而言即兼容类型。同样的规则应用到函数调用和左值访问;实参类型必须与形参类型兼容,而左值表达式类型必须与被访问对象的类型兼容

类型 TU 兼容,若

  • 它们是同一类型(同名或由 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++ 程序。

合成类型

合成类型能从二个兼容的类型构造;它是与两个类型兼容,并满足下列条件的类型:

  • 若两个类型均为数组类型,则应用下列规则:
  • 若一个类型是常量大小数组,则合成类型为该大小的数组。
  • 否则,若一个类型是 VLA ,其大小由表达式指定且表达式尚未求值,则需要两个类型的合成类型的程序有未定义行为。
  • 否则,若一个类型为已指定大小的 VLA ,则合成类型为该大小的 VLA 。
  • 否则,若一个类型为未指定大小的 VLA ,则合成类型为未指定大小的 VLA 。
(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-specifierstype-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

参阅