我们通常用类型和存储类别来描述一个变量。C90新增了两个属性:恒常性和易变性,这两个属性可以分别用关键字const和volatile来声明,以这两个关键字创建的类型是限定类型;C99标准新增了第三个限定符:restrict,用于提高编译器优化;C11标准新增了第四个限定符:_Atomic,C11提供一个可选库,由stdatomic.h管理,以支持并发程序设计,而_Atomic是可选支持项。C99为类型限定符增加了一个新属性:幂等,其意义是可以在一条声明中多次使用同一个限定符,多余的限定符将被忽略。
1.volatile类型限定符
volatile限定符告知计算机,代理(非变量所在的程序)可以改变变量的值。通常,它被用于硬件地址以及在其它程序或同时运行的线程中共享数据。并且volatile设计编译器的优化。
val1 = x;
/*中间是一些不涉及x的代码*/
val2 = x;
进行优化的编译器会注意到以上代码使用了两次x,但并未改变它的值。于是编译器把x的值临时存储在寄存器中,然后val2需要使用x时,才从寄存器中读取x的值,以节约时间。这个过程被称为高速缓存。高速缓存是一个不错的优化方案,但是如果一些代理再以上两条语句间改变了x的值,就不能这样进行优化了,如果没有volatile关键字,编译器会假定变量的值在使用过程中不发生改变,然后再尝试优化代码。可以同时使用volatile和const限定一个变量,通常用const把硬件时间设置为不可更改的变量,但是可以通过代理改变,这时用volatile。只能在声明时同时使用这两个限定符,顺序无关紧要。
2.restrict类型限定符
restrict关键字允许编译器优化某部分代码以更好地支持计算。它只能用于指针,表明该指针是访问数据对象的唯一且初始的方式。
int ar[10];
int * restrict restar = (int *) malloc(10 * sizeof(int));
int * par = ar;
……
for(int n = 0; n < 10; n++){
par[n] += 5;
restar[n] += 5;
ar[n] *= 2;
par[n] += 3;
restar[n] += 3;
}
由于之前声明了restar是访问它所指向的数据块唯一且初始的方式,编译器可以把涉及restar的两条语句替换成restar[n] += 8;
,效果相同。但是par不行,因为中间ar[n]改变了数据的值。如上,如果使用了restrict关键字,编译器就可以选择捷径优化计算。
restrict限定符还可用于函数形参中的指针,这意味着编译器可以假定在函数体内其他标识符不会修改该指针指向的数据,而且编译器可以尝试对其优化,使其不做别的用途。
3._Atomic类型限定符
并发程序设计把程序执行分成可以同时执行的多个线程(操作系统知识),不过多赘述。值得注意的是,要通过各种宏函数来访问原子类型;并且一个线程对一个原子类型的对象进行原子操作(原子操作即一气呵成的操作,通过关中断和开中断实现)时,其它线程不能访问该对象。
int hogs; //普通声明
hogs = 12; //普通赋值
_Atomic int hogs; //声明一个原子类型的变量
atomic_store(&hogs, 12); //stdatomic.h中的宏,这是一个原子过程,此时其它程序不能访问hogs
4.旧关键字的新位置
C99允许把类型限定符和存储类别说明符static放在函数原型和函数头的形式参数的初始方括号中。
对于类型限定符而言,这样做为现有功能提供了一个替代的语法。根据新标准,在声明形式参数时,指针表示法和数组表示法都可以使用这两个限定符。
void fun(int * restrict a1, int * const a2, int n); //旧式语法
void fun(int a1[restrict], int a2[const], int n); //C99允许
static的情况不同,因为新的标准为static引入了一种新的与以前毫不相关语法。现在,static除了表明静态存储类别变量的作用域或链接外,新的用法告知编译器如何使用形式参数。例如,double stick(double ar[static 20]);
,static的这种用法表明,函数调用中的实际参数应该是一个指向数组首元素的指针,且该数组至少有20个元素。这种用法的目的是让编译器使用这些信息优化函数的编码。