当前位置: C语言 -- 基础 -- 兼容类型

兼容类型(一)

如果两种类型无需修改即可一起使用、无需修改即可相互替换,那么这两种类型是兼容类型。同一作用域内同一对象或者函数的所有声明都应是兼容类型;否则其行为是未定义的。相同类型一定是兼容类型;但兼容类型不一定是相同类型。本文就基本类型,结构类型、联合类型、枚举类型,指针类型,数组类型,函数类型等类型讨论一下C语言中的类型兼容问题。


一、基本类型的兼容性

C语言中基本类型(basic types)及其相互关系如下图所示:

基本类型    { 整数类型    { char类型   标准整数类型    { 标准有符号整数类型    { signed char类型   short int类型   int类型   long int类型   long long int类型   标准无符号整数类型    { bool类型   unsigned char类型   unsigned short int类型   unsigned int类型   unsigned long int类型   unsigned long long int类型   扩展整数类型   位精确整数类型    { 位精确有符号整数类型   位精确无符号整数类型   浮点类型    { 实数浮点类型    { 标准浮点类型    { float类型   double类型   long double类型   十进制浮点类型    { _Decimal32类型   _Decimal64类型   _Decimal128类型   复数浮点类型    { float _Complex类型   double _Complex类型   long double _Complex类型

:枚举类型虽属于整数类型,但ISO/IEC 9899:2024标准并未将其列入基本类型。)


如果两个类型是相同类型,那么这两个类型一定是兼容类型。根据ISO/IEC 9899:2024标准第6.7.3 Type specifiers节,下面同一行中逗号分隔的类型说明符表示相同的类型。对于位字段,int类型与signed int类型是相同类型,还是与unsigned int类型是相同类型,将由实现定义。

- short, signed short, short int, signed short int

- unsigned short, unsigned short int

- int, signed, signed int

- unsigned, unsigned int

- long, signed long, long int, signed long int

- unsigned long, unsigned long int

- long long, signed long long, long long int, signed long long int

- unsigned long long, unsigned long long int

- _BitInt(constant-expression), signed _BitInt(constant-expression)


short a = 0;
signed short b = 0;
short int c = 0;
signed short int d = 0;

变量abcd的类型是相同类型,也是兼容类型。


通过typedef引入的别名如果与对应的内置类型(built-in types)是相同类型,它们之间也是兼容的。

typedef unsigned Money;
Money income = 0;
unsigned expenditure = 0;

这里Moneyunsigned是相同类型,也是兼容类型。


上述类型都是非限定类型(unqualified type);如果存在类型限定符(type qualifier),则类型是限定类型(qualified type)。每个非限定类型都存在多个对应的限定类型。非限定类型与其对应的限定类型之间的关系是属于相同类型类别且具有相同表示形式和对齐要求的不同类型;作为函数参数、函数返回值、联合成员时,它们之间可以互换。如果存在类型限定符,兼容类型的类型限定符应该是相同的,并且对应的非限定类型也应该是兼容的。


typedef unsigned Money;
const Money income = 0;
volatile unsigned expenditure = 0;
const unsigned profit = 0;

const Moneyconst unsigned是兼容类型,但它们与volatile unsigned不兼容。


如果存在多个类型限定符,类型限定符的先后顺序不影响类型的兼容性。

const volatile int a = 0;
volatile const int b = 0;

变量ab的类型是兼容类型。


如果一个对象的存储值从另一个对象读取,并且这两个对象的存储以某种方式重叠,那么这种重叠应该是精确的,并且两个对象的类型应该是兼容类型的限定版本或者非限定版本,否则其行为是未定义的。

struct s1 {int i;};
struct s2 {int i;};

union u {
  struct s1 s11;
  struct s2 s21;
  struct s1 s12;
};
...
union u data = {5};
data.s12 = data.s11;    //合法。
data.s21 = data.s12;    //未定义行为。

struct s1struct s2不是兼容类型,所以data.s12data.s21之间的赋值是未定义行为。

重叠操作数之间赋值最常见的情况是联合;在联合中通常需要将一个字段赋值给另一个字段,以实现类型转换。简单情况下赋值可能都能正常进行,但不能最大限度地保证代码的可移植性。可移植性高的代码应在这种赋值中使用临时变量作为中间变量。


同一泛型选择(generic selection)中任意两个泛型关联(generic associations)不能表示兼容类型。

#define ABS(x) _Generic((x),	\
              int: abs,		\
              signed: abs,	\
              long int: labs,	\
              default: llabs	\
              )(x)

上述泛型选择中int类型和signed类型是相同类型,也是兼容类型;上述代码编译时会给出类似error: _Generic specifies two compatible types的出错信息。


泛型选择控制表达式的类型就像经过转换(例如:左值转换、数组到指针的转换、函数到指针的转换)后的表达式类型;控制表达式的类型最多只能与泛型关联列表中的一种类型兼容。如果一个泛型选择没有默认的泛型关联,其控制表达式的类型应与泛型关联列表中一种命名类型兼容。

如果泛型选择中存在一个类型与控制表达式类型兼容的泛型关联,那么泛型选择的结果表达式就是该泛型关联中的表达式;否则泛型选择的结果表达式是默认泛型关联中的表达式。

#define ABS(x) _Generic((x),	\
              int: abs,		\
              long int: labs,	\
              default: llabs	\
              )(x)

对于上述泛型选择,ABS(5)中控制表达式的类型与int类型兼容;ABS(5L)中控制表达式的类型与long int类型兼容;ABS(5LL)中控制表达式的类型与long long int类型兼容。