声明(十)
四、类型限定符
C语言类型限定符包括:const、volatile、restrict、_Atomic;其中_Atomic类型限定符请参阅_Atomic关键词,本章节主要讨论const、volatile、restrict类型限定符。
类型限定符仅对左值表达式有意义。实现可以将非volatile的const对象放在只读存储区;此外如果该对象地址从未使用,实现无需为其分配存储空间。
同一类型限定符在同一说明符限定符列表中或者作为声明说明符出现多次(无论是直接出现,还是通过typedef名或者typeof说明符出现。),其行为与只出现一次相同。
typedef const int Cint; const Cint ci = 5; //等价于Cint ci = 5;。 const typeof(ci) a = 3; //等价于const int a = 3;。
如果其它类型限定符(const、volatile、restrict)与_Atomic类型限定符一起使用,结果类型是限定的对应类型的原子版本。
_Atomic const int *ptr; //等价于const atomic_int *ptr;。
如果两种限定类型兼容,其非限定版本的对应类型应兼容,并且类型限定符应相同。
typedef const int Ci; /*ci1、ci2、ci3具有兼容类型。*/ const int ci1 = 1; const signed ci2 = 2; Ci ci3 = 3;
如果说明符或者限定符列表中存在多个类型限定符,类型限定符的顺序不影响指定类型。
_Atomic const int *ptr; //等价于const _Atomic int *ptr;。
如果数组声明包含类型限定符,类型限定符同时作用于数组和数组元素。
(注:该规则不适用于_Atomic类型限定符;_Atomic类型限定符对数组类型本身没有直接影响,但会影响引用数组类型的指针类型的转换规则。)
const int arr[3] = {1, 2, 3}; int *p = NULL; const int *cp = NULL; arr[0] = 5; //非法。 p = &arr[0]; //非法,&arr[0]的类型是const int *。 cp = &arr[0]; //合法。
如果类型限定符应用于函数类型,其行为是未定义的。
typedef int Func(int); const Func func;
上述代码使用Visual Studio编译时,会给出类似“warning C4180: 应用到函数类型的限定符没有意义;”的警告。
1、const类型限定符
使用const类型限定符声明的变量初始化后,其值视为是不变的,任何对该变量的赋值、自增、自减操作都是非法的。
const int ci = 5; ci = 10; //非法。 ci++; //非法。 ci--; //非法。
const类型限定符用于指针声明时,类型限定符出现的位置不同,意义是不一样的。
int i; const int *p1 = NULL; int * const p2 = &i;
指针p1指向的对象是值不变的int类型对象,可以给指针p1赋值。指针p2指向的位置是不变的,指向int类型变量i;任何对指针p2的赋值都是非法的。
指针赋值时,左操作数指向对象的类型应具有右操作数指向对象类型的所有类型限定符。
int i; int *p1; const int *p2; int * const p3 = &i; //合法,右操作数指向类型为int类型。 const int * const p4 = &i; //合法,右操作数指向类型为int类型。 p1 = p2; //非法,右操作数指向类型为const int类型。 p2 = p1; //合法,右操作数指向类型为int类型。 p2 = p3; //合法,右操作数指向类型为int类型。 p2 = p4; //合法,右操作数指向类型为const int类型。
如果试图通过非const限定类型的左值修改const限定类型定义的对象,其行为是未定义的。
const int ci = 5; int *p = NULL; const int *cp = &ci; p = (int *)cp; *p = 10; //未定义行为。
上述代码根据ISO/IEC 9899:2024标准是未定义行为;具体实现中主流编译器都能正常编译,GCC、Visual Studio、Pelles C执行结果是ci值为10;ideone执行结果是ci值为5。
2、volatile类型限定符
使用volatile类型限定符声明的变量,其值可能在程序控制外访问或者更改。
编译器编译时通常会执行一些优化,以减少读/写次数。
int i = 0; i++; i--;
上述代码优化后可能只在变量i中存储0,而没有自增和自减。如果变量i与其它程序共享,或者以实现未知的方式修改,或者产生未知副作用,上述优化是不合适的,使用volatile类型限定符可以防止这种优化。任何使用此类对象的表达式都应严格按照抽象机的规则进行评估。在每个序列点,对象最后存储值应与抽象机规定值一致,除非被前面提到的未知因素修改。
volatile声明可用于描述与内存映射输入/输出端口相对应的对象或者异步中断函数访问的对象。除非表达式求值规则允许,否则不允许实现对此类声明的对象进行优化或者重排。对volatile类型限定符限定对象的访问将由实现定义。
extern const volatile int real_time_clock;
real_time_clock可能由硬件修改,但不能赋值、递增或者递减。
如果试图通过非volatile类型限定符限定的左值引用volatile类型限定符限定的对象,其行为是未定义的。
volatile int vi = 0; int *p = NULL; volatile int *vp = NULL; p = &vi; //未定义行为。 vp = &vi; //合法。