volatile and restrict type qualifiers
C89 标准委员会为 C 添加了两个限定词,const 和 volatile。C99 委员会添加了第三个 restrict 限定词。这三个限定词以及他们的结合决定了当编译器通过一个左值( lvalue,暂且翻译成左值 )访问一个对象时所做的一些假设。左值就是即可以出现在赋值语句左边也可以是右边的对象。
const 的语法和语义是从 C++ 那里改编来的,const 这个概念在许多其他语言里都有。volatile 和 restrict 是委员会的发明,两者都遵从 const 的语法模式。
类型限定词的作用是控制对代码的优化。有几项重要的优化技术是建立在缓冲原理之上的:在某些情况下,编译器可以使用寄存器保存最近一次通过地址访问的值,并且当再次访问同一地址时使用这个记住的值( CPU 寄存器或高速缓冲是典型的硬件寄存器)。如果使用寄存器而不是外部的存储器,代码可以变得更小更快。
类型限定词可以用他们在访问和缓冲上所作的限制来描述:
const
用 const 限定的左值不可写。当缺少此限定词时就允许写操作。
volatile
用 volatile 限定的左值没有缓存,也就是说直接从存储器中读取或写回存储器。volatile 可以用来限定一些无法保证或编译器无法发现其值是否改变的对象( 例如,由系统时钟更新的变量 )。
写嵌入式代码的人应该很熟悉这个限定词,举个简单的例子:
一个8位的外设状态寄存器被映射到存储器地址0x1234。现在要求轮询此状态寄存器直到它变为非零。以下是错误的实现:
//wait for register to become non-zero
while (*pReg == 0) {} //do sth. else
当你打开编译器优化选项时,将生成如下汇编代码:
mov a, @ptr
loop:
bz loop
优化原理相当简单,就是记住最近访问的值,把它缓存在累加器中。但是 loop 陷入了死循环,因为在累加器的值永远不会改变。正确的方法就是使用 volatile 限定词:
//wait for register to become non-zero
while (*pReg == 0) {} //do sth. else
编译器产生如下汇编代码:
loop:
mov a, @ptr
bz loop
这才是我们真正期望的结果。
restrict
restrict 只能使用于指针,使用 restrict 限定的指针和它所引用的对象之间有特殊的关联:只能直接或间接使用此指针或基于此指针的表达式访问其引用的对象。这样编译器可以生成优化代码。
举个例子,有如下的函数:
{
*ptrA += *val;
*ptrB += *val;
}
ptrA,ptrB 和 val 可能指向同一个内存块,编译器不能彻底优化代码,生成的代码如下:
load R2 ← *ptrA ; Load the value of ptrA ponter
add R2 += R1 ; Perform Addition
set R2 → *ptrA ; Update the value of ptrA pointer
; Similarly for ptrB, note that val is loaded twice,
; Because ptrA may point to val.
load R1 ← *val
load R2 ← *ptrB
add R2 += R1
set R2 → *ptrB
使用 restrict 后,
编译器知道三个指针指向不同的内存块,修改其中一个指针不会影响其他两个,所以生成了优化程度更高的代码:
load R2 ← *ptrA
load R3 ← *ptrB
add R2 += R1
add R3 += R1
set R2 → *ptrA
set R3 → *ptrB
注意: val 只载入寄存器一次。
